The DSL
The DSL is generated via quicktype, from the Vega Schema.
This is a simple bar chart.
import viz.dsl.vegaLite.*
import viz.dsl.DslPlot.*
import viz.dsl.DslSpec
import viz.PlotTargets.doNothing
import io.circe._
val someData : InlineDataset = Seq(
Map("a" -> Some(Json.fromString("A")), "b" -> Some(Json.fromInt(20))),
Map("a" -> Some(Json.fromString("Next")), "b" -> Some(Json.fromInt(25))),
Map("a" -> Some(Json.fromString("embiggen")), "b" -> Some(Json.fromInt(5)))
)
val theChart = VegaLiteDsl(
`$schema` = Some("https://vega.github.io/schema/vega-lite/v5.json"),
description = Some("A simple bar chart that is kinda statically typed"),
data = Some(
URLData(
values = Some(
someData
)
)
),
height = Some("container"),
width = Some("container"),
mark = Some("bar".asInstanceOf[AnyMark]),
encoding = Some(
EdEncoding(
x = Some(
XClass(
field = Some("a".asInstanceOf[Field]),
`type` = Some("nominal".asInstanceOf[viz.dsl.vegaLite.Type]),
axis = Some(Axis(labelAngle = Some(45.asInstanceOf[LabelAngle]))),
)
),
y = Some(
YClass(
field = Some("b".asInstanceOf[Field]),
`type` = Some("quantitative".asInstanceOf[viz.dsl.vegaLite.Type]),
)
)
)
)
)
viz.js.showChartJs(DslSpec(theChart), node)
That is... a lot of work though. Writing this out by hand would be formidably hard. Honestly, no one is going to do that. New plan...
Cheat! We have a strongly typed representation of the schema, so why not, once again... start from the examples.
Simpler strategy
And seeing as we have a case class... copy...
import viz.dsl.vegaLite.*
import viz.dsl.DslPlot.*
import viz.dsl.DslSpec
import viz.PlotTargets.doNothing
import viz.vega.plots.*
import io.circe._
val teehee = SpecUrl.SimpleBarChartLite.toDsl()
val asDsl = teehee.toOption.get.asInstanceOf[VegaLiteDsl]
val someData : InlineDataset = Seq(
Map("a" -> Some(Json.fromString("A")), "b" -> Some(Json.fromInt(20))),
Map("a" -> Some(Json.fromString("Next")), "b" -> Some(Json.fromInt(25))),
Map("a" -> Some(Json.fromString("embiggen")), "b" -> Some(Json.fromInt(5)))
)
val cheatingCanBeGood = asDsl.copy(
data = Some(URLData(values = Some(someData))),
width = Some("container"),
height = Some("container")
)
viz.js.showChartJs(DslSpec(cheatingCanBeGood), node)
To give an idea of the types, let's run this again in scala JVM
import viz.dsl.vegaLite.*
import viz.dsl.DslPlot.*
import viz.dsl.DslSpec
import viz.PlotTargets.doNothing
import viz.vega.plots.*
import io.circe._
val teehee = SpecUrl.SimpleBarChartLite.toDsl() // Cunningly parse JSON here
// teehee: Either[Error, VegaLiteDsl | VegaDsl] = Right(
// value = VegaLiteDsl(
// $schema = Some(value = "https://vega.github.io/schema/vega-lite/v5.json"),
// align = None,
// autosize = None,
// background = None,
// bounds = None,
// center = None,
// config = None,
// data = Some(
// value = URLData(
// format = None,
// name = None,
// url = None,
// values = Some(
// value = List(
// Map(
// "a" -> Some(value = JString(value = "A")),
// "b" -> Some(value = JNumber(value = JsonLong(value = 28L)))
// ),
// Map(
// "a" -> Some(value = JString(value = "B")),
// "b" -> Some(value = JNumber(value = JsonLong(value = 55L)))
// ),
// Map(
// "a" -> Some(value = JString(value = "C")),
// "b" -> Some(value = JNumber(value = JsonLong(value = 43L)))
// ),
// Map(
// "a" -> Some(value = JString(value = "D")),
// "b" -> Some(value = JNumber(value = JsonLong(value = 91L)))
// ),
// Map(
// "a" -> Some(value = JString(value = "E")),
// "b" -> Some(value = JNumber(value = JsonLong(value = 81L)))
// ),
// Map(
// "a" -> Some(value = JString(value = "F")),
// "b" -> Some(value = JNumber(value = JsonLong(value = 53L)))
// ),
// Map(
// "a" -> Some(value = JString(value = "G")),
// "b" -> Some(value = JNumber(value = JsonLong(value = 19L)))
// ),
// Map(
// "a" -> Some(value = JString(value = "H")),
// "b" -> Some(value = JNumber(value = JsonLong(value = 87L)))
// ),
// Map(
// ... // Cunningly parse JSON here
val asDsl = teehee.toOption.get.asInstanceOf[VegaLiteDsl]
// asDsl: VegaLiteDsl = VegaLiteDsl(
// $schema = Some(value = "https://vega.github.io/schema/vega-lite/v5.json"),
// align = None,
// autosize = None,
// background = None,
// bounds = None,
// center = None,
// config = None,
// data = Some(
// value = URLData(
// format = None,
// name = None,
// url = None,
// values = Some(
// value = List(
// Map(
// "a" -> Some(value = JString(value = "A")),
// "b" -> Some(value = JNumber(value = JsonLong(value = 28L)))
// ),
// Map(
// "a" -> Some(value = JString(value = "B")),
// "b" -> Some(value = JNumber(value = JsonLong(value = 55L)))
// ),
// Map(
// "a" -> Some(value = JString(value = "C")),
// "b" -> Some(value = JNumber(value = JsonLong(value = 43L)))
// ),
// Map(
// "a" -> Some(value = JString(value = "D")),
// "b" -> Some(value = JNumber(value = JsonLong(value = 91L)))
// ),
// Map(
// "a" -> Some(value = JString(value = "E")),
// "b" -> Some(value = JNumber(value = JsonLong(value = 81L)))
// ),
// Map(
// "a" -> Some(value = JString(value = "F")),
// "b" -> Some(value = JNumber(value = JsonLong(value = 53L)))
// ),
// Map(
// "a" -> Some(value = JString(value = "G")),
// "b" -> Some(value = JNumber(value = JsonLong(value = 19L)))
// ),
// Map(
// "a" -> Some(value = JString(value = "H")),
// "b" -> Some(value = JNumber(value = JsonLong(value = 87L)))
// ),
// Map(
// "a" -> Some(value = JString(value = "I")),
// ...
val someData : InlineDataset = Seq(
Map("a" -> Some(Json.fromString("A")), "b" -> Some(Json.fromInt(20))),
Map("a" -> Some(Json.fromString("Next")), "b" -> Some(Json.fromInt(25))),
Map("a" -> Some(Json.fromString("embiggen")), "b" -> Some(Json.fromInt(5)))
)
// someData: Map[String, Option[Json]] | String | Seq[InlineDatasetElement] = List(
// Map(
// "a" -> Some(value = JString(value = "A")),
// "b" -> Some(value = JNumber(value = JsonLong(value = 20L)))
// ),
// Map(
// "a" -> Some(value = JString(value = "Next")),
// "b" -> Some(value = JNumber(value = JsonLong(value = 25L)))
// ),
// Map(
// "a" -> Some(value = JString(value = "embiggen")),
// "b" -> Some(value = JNumber(value = JsonLong(value = 5L)))
// )
// )
asDsl.copy(
data = Some(URLData(values = Some(someData))),
width = Some("container"),
height = Some("container")
)
// res0: VegaLiteDsl = VegaLiteDsl(
// $schema = Some(value = "https://vega.github.io/schema/vega-lite/v5.json"),
// align = None,
// autosize = None,
// background = None,
// bounds = None,
// center = None,
// config = None,
// data = Some(
// value = URLData(
// format = None,
// name = None,
// url = None,
// values = Some(
// value = List(
// Map(
// "a" -> Some(value = JString(value = "A")),
// "b" -> Some(value = JNumber(value = JsonLong(value = 20L)))
// ),
// Map(
// "a" -> Some(value = JString(value = "Next")),
// "b" -> Some(value = JNumber(value = JsonLong(value = 25L)))
// ),
// Map(
// "a" -> Some(value = JString(value = "embiggen")),
// "b" -> Some(value = JNumber(value = JsonLong(value = 5L)))
// )
// )
// ),
// sequence = None,
// sphere = None,
// graticule = None
// )
// ),
// datasets = None,
// description = Some(value = "A simple bar chart with embedded data."),
// encoding = Some(
// value = EdEncoding(
// angle = None,
// color = None,
// column = None,
// description = None,
// detail = None,
// facet = None,
// fill = None,
// fillOpacity = None,
// href = None,
// key = None,
// latitude = None,
// ...
Discussion
Typesafety is nice to have in the sense that it removes entire categories of "unplottable" states. However, many charts that typecheck, will not make sense. Given the flexibility of the vega schema, typesafety has a high mental burden.