
smile.plot.vega.package.scala Maven / Gradle / Ivy
The newest version!
/*******************************************************************************
* Copyright (c) 2010-2020 Haifeng Li. All rights reserved.
*
* Smile is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation, either version 3 of
* the License, or (at your option) any later version.
*
* Smile is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Smile. If not, see .
******************************************************************************/
package smile.plot
import scala.language.implicitConversions
import smile.data._, smile.data.`type`.DataType
import smile.json._
/** Vega-lite based data visualization.
*
* @author Haifeng Li. All rights reserved.
*/
package object vega {
implicit def pimpDataFrame(data: DataFrame): DataFrame2JSON = DataFrame2JSON(data)
implicit def pimpTuple(data: Tuple): Tuple2JSON = Tuple2JSON(data)
/** Returns the HTML of plot specification with Vega Embed. */
def embed(spec: JsObject): String = {
s"""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
""".stripMargin
}
/** Returns the HTML wrapped in an iframe to render in notebooks.
* @param id the iframe HTML id.
*/
def iframe(spec: JsObject, id: String = java.util.UUID.randomUUID.toString): String = {
val src = xml.Utility.escape(embed(spec))
s"""
|
|
""".stripMargin
}
/** Scatter plot.
*
* @param data a n-by-2 matrix
* @param fields the field names.
* @param color optional (name, nominal variable) pair for different color of dots.
* @param shape optional (name, nominal variable) pair for different shape of dots.
* @param sizeOrText optional (name, variable) pair for the size of dots (bubble plot) or text marks.
* @param properties additional vega-lite specification properties.
*/
def plot(data: Array[Array[Double]],
fields: (String, String) = ("x", "y"),
color: Option[(String, Either[Array[Int], Array[String]])] = None,
shape: Option[(String, Either[Array[Int], Array[String]])] = None,
sizeOrText: Option[(String, Either[Array[Double], Array[String]])] = None,
properties: JsObject = JsObject()): JsObject = {
spec(
valuesOf(data, fields, color, shape, sizeOrText),
mark = "point",
x = JsObject("field" -> fields._1, "type" -> "quantitative"),
y = JsObject("field" -> fields._2, "type" -> "quantitative"),
color = color.map{case (name, _) => JsObject("field" -> name, "type" -> "nominal")},
shape = shape.map{case (name, _) => JsObject("field" -> name, "type" -> "nominal")},
size = sizeOrText.map{case (name, _) => JsObject("field" -> name, "type" -> "quantitative")},
text = sizeOrText.map{case (name, _) => JsObject("field" -> name, "type" -> "nominal")}
) ++= properties
}
/** Scatter plot matrix. */
def spm(data: DataFrame, clazz: Option[String] = None, properties: JsObject = JsObject()): JsObject = {
val columns = clazz match {
case None => data.names
case Some(clazz) =>
properties.spec =
json"""
|{
| "encoding": {
| "color": {
| "condition": {
| "selection": "brush",
| "field": "$clazz",
| "type": "nominal"
| },
| "value": "grey"
| }
| }
|}
"""
data.names.filter(name => name != clazz)
}
val names: JsArray = columns.map(s => JsString(s))
val values = data.toJSON
val spec = JsonParser(s"""
|{
| "$$schema": "https://vega.github.io/schema/vega-lite/v4.json",
| "repeat": {
| "row": $names,
| "column": $names
| },
| "spec": {
| "data": {
| "values": $values
| },
| "mark": "point",
| "selection": {
| "brush": {
| "type": "interval",
| "resolve": "union",
| "on": "[mousedown[event.shiftKey], window:mouseup] > window:mousemove!",
| "translate": "[mousedown[event.shiftKey], window:mouseup] > window:mousemove!",
| "zoom": "wheel![event.shiftKey]"
| },
| "grid": {
| "type": "interval",
| "resolve": "global",
| "bind": "scales",
| "translate": "[mousedown[!event.shiftKey], window:mouseup] > window:mousemove!",
| "zoom": "wheel![!event.shiftKey]"
| }
| },
| "encoding": {
| "x": {"field": {"repeat": "column"}, "type": "quantitative"},
| "y": {
| "field": {"repeat": "row"},
| "type": "quantitative",
| "axis": {"minExtent": 30}
| }
| }
| }
|}
""".stripMargin)
spec.asInstanceOf[JsObject] ++= properties
}
/** Line plot.
*
* @param data a n-by-2 matrix with variable names.
* @param point show point mark if true.
* @param color optional nominal name-variable pair for different color of dots.
* @param shape optional nominal name-variable pair for different shape of dots.
* @param properties additional vega-lite specification properties.
*/
def line(data: Array[Array[Double]],
fields: (String, String) = ("x", "y"),
point: Boolean = false,
color: Option[(String, Either[Array[Int], Array[String]])] = None,
shape: Option[(String, Either[Array[Int], Array[String]])] = None,
properties: JsObject = JsObject()): JsObject = {
spec(
valuesOf(data, fields, color, shape, None),
mark = if (point) JsObject("type" -> "line", "point" -> true) else "line",
x = JsObject("field" -> fields._1, "type" -> "quantitative"),
y = JsObject("field" -> fields._2, "type" -> "quantitative"),
color = color.map{case (name, _) => JsObject("field" -> name, "type" -> "nominal")},
shape = shape.map{case (name, _) => JsObject("field" -> name, "type" -> "nominal")}
) ++= properties
}
/** Box plot.
* @param data an array.
* @param group the group of each data element.
* @param properties additional vega-lite specification properties.
*/
def boxplot(data: (String, Array[Double]), group: (String, Array[String]), properties: JsObject = JsObject()): JsObject = {
spec(
valuesOf(data, group),
mark = JsObject("type" -> "boxplot", "extent" -> 1.5),
x = JsObject("field" -> group._1, "type" -> "ordinal"),
y = JsObject("field" -> data._1, "type" -> "quantitative")
) ++= properties
}
/** Bar chart.
* @param data an array.
*/
def bar(data: Array[Double]): JsObject = {
bar(("y", data), ("x", data.map(i => i.toString)))
}
/** Bar chart.
* @param data an array.
* @param group the group of each data element.
* @param properties additional vega-lite specification properties.
*/
def bar(data: (String, Array[Double]), group: (String, Array[String]), properties: JsObject = JsObject()): JsObject = {
spec(
valuesOf(data, group),
mark = "bar",
x = JsObject("field" -> group._1, "type" -> "ordinal"),
y = JsObject("field" -> data._1, "type" -> "quantitative")
) ++= properties
}
/** Histogram plot. */
def hist(x: (String, Array[Double]), k: Int, properties: JsObject = JsObject()): JsObject = {
spec(valuesOf(x._1, x._2), "bar",
x = JsObject("bin" -> JsObject("maxbins" -> JsInt(k)), "field" -> x._1, "type" -> "quantitative"),
y = JsObject("aggregate" -> "count", "type" -> "quantitative")
) ++= properties
}
/** Returns a JsObject with the field 'values' of an array of data records. */
private[vega] def valuesOf(data: Array[Array[Double]],
fields: (String, String) = ("x", "y"),
color: Option[(String, Either[Array[Int], Array[String]])],
shape: Option[(String, Either[Array[Int], Array[String]])],
sizeOrText: Option[(String, Either[Array[Double], Array[String]])]): JsObject = {
val (x, y) = fields
val values: JsArray = (0 until data.length).map { i =>
val row = data(i)
val obj = JsObject(x -> row(0), y -> row(1))
color map { case (name, color) => obj(name) = color.fold(a => JsInt(a(i)), a => JsString(a(i)))}
shape map { case (name, shape) => obj(name) = shape.fold(a => JsInt(a(i)), a => JsString(a(i)))}
sizeOrText map { case (name, sizeOrText) => obj(name) = sizeOrText.fold(a => JsDouble(a(i)), a => JsString(a(i)))}
obj
}
JsObject("values" -> values)
}
/** Returns a JsObject with the field 'values' of an array of data records. */
private[vega] def valuesOf(field: String, x: Array[Double]): JsObject = {
val values: JsArray = (0 until x.length).map(i => JsObject(field -> JsDouble(x(i))))
JsObject("values" -> values)
}
/** Returns a JsObject with the field 'values' of an array of data records. */
private[vega] def valuesOf(data: (String, Array[Double]), group: (String, Array[String])): JsObject = {
val x = group._2
val y = data._2
val values: JsArray = (0 until x.length).map(i => JsObject(group._1 -> x(i), data._1 -> y(i)))
JsObject("values" -> values)
}
/** Returns the plot specification. */
private[vega] def spec(data: JsObject,
mark: JsValue,
x: JsObject = JsObject("field" -> "x", "type" -> "quantitative"),
y: JsObject = JsObject("field" -> "y", "type" -> "quantitative"),
color: Option[JsObject] = None,
shape: Option[JsObject] = None,
size: Option[JsObject] = None,
text: Option[JsObject] = None
): JsObject = {
val spec = JsObject(
"$schema" -> "https://vega.github.io/schema/vega-lite/v4.8.1.json",
"width" -> "container",
"height" -> "container",
"data" -> data,
"mark" -> mark,
"encoding" -> JsObject(
"x" -> x,
"y" -> y
)
)
color match {
case Some(color) => spec.encoding.color = color
case None => ()
}
shape match {
case Some(shape) => spec.encoding.shape = shape
case None => ()
}
size match {
case Some(size) => spec.encoding.size = size
case None => ()
}
text match {
case Some(text) => spec.encoding.text = text
case None => ()
}
spec
}
}
package vega {
import smile.data.measure.DiscreteMeasure
private[vega] case class DataFrame2JSON(data: DataFrame) {
def toJSON: JsArray = {
JsArray((0 until data.nrows).map { i => Tuple2JSON(data(i)).toJSON }: _*)
}
}
private[vega] case class Tuple2JSON(data: Tuple) {
def toJSON: JsObject = {
val schema = data.schema
JsObject((0 until data.length).map(i => schema.fieldName(i) -> get(i)): _*)
}
private def get(i: Int): JsValue = {
val schema = data.schema
val field = schema.field(i)
if (field.measure.isInstanceOf[DiscreteMeasure]) {
JsString(data.getString(i))
} else {
field.`type`.id match {
case DataType.ID.Boolean => JsBoolean(data.getBoolean(i))
case DataType.ID.Byte => JsInt(data.getByte(i))
case DataType.ID.Short => JsInt(data.getShort(i))
case DataType.ID.Integer => JsInt(data.getInt(i))
case DataType.ID.Long => JsLong(data.getLong(i))
case DataType.ID.Float => JsDouble(data.getFloat(i))
case DataType.ID.Double => JsDouble(data.getDouble(i))
case _ => JsString(data.getString(i))
}
}
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy