Mixed typesafe / mutable
Start mutable and add a little safety
The "mutuable" approach combines the previous two ideas.We could use a "typesafe" part of the DSL, to create the part we want, then pickle it to JSON and put it in our spec...
import viz.dsl.vega.*
import viz.vega.plots.{BarChart, given}
import viz.dsl.Conversion.u
val axisOrient : TitleOrientEnum = TitleOrientEnum.top
val newAxis : Axis= Axis(orient = axisOrient, scale = "xscale")
val chart = BarChart(
List(
viz.Utils.removeXAxis,
viz.Utils.fillDiv,
spec => spec("axes") = spec("axes").arr :+ newAxis.u
)
)
viz.js.showChartJs(chart, node)
Note that we get some typechecking.
import viz.dsl.vega.*
val axisOrient : TitleOrientEnum = "Not an orientation" // This can one of the TitleOrientEnum values... visit in IDE for help.
// error:
// Found: ("Not an orientation" : String)
// Required: viz.dsl.vega.TitleOrientEnum
// val parsed : Either[io.circe.Error, viz.dsl.vega.Axis] = decode[Axis](copyPastedAxisFromExample)
// ^
We can also take this to greater extremes, and in a sort of magpie style, parse more complex parts of the spec.
Shortcuts
import viz.dsl.vega.*
import io.circe._, io.circe.parser._
// Steal a part of the spec you want from an example.
val copyPastedAxisFromExample = """
{ "orient": "top", "scale": "xscale" }
"""
// copyPastedAxisFromExample: String = """
// { "orient": "top", "scale": "xscale" }
// """
val parsed : Either[io.circe.Error, viz.dsl.vega.Axis] = decode[Axis](copyPastedAxisFromExample)
// parsed: Either[Error, Axis] = Right(
// value = Axis(
// aria = None,
// bandPosition = None,
// description = None,
// domain = None,
// domainCap = None,
// domainColor = None,
// domainDash = None,
// domainDashOffset = None,
// domainOpacity = None,
// domainWidth = None,
// encode = None,
// format = None,
// formatType = None,
// grid = None,
// gridCap = None,
// gridColor = None,
// gridDash = None,
// gridDashOffset = None,
// gridOpacity = None,
// gridScale = None,
// gridWidth = None,
// labelAlign = None,
// labelAngle = None,
// labelBaseline = None,
// labelBound = None,
// labelColor = None,
// labelFlush = None,
// labelFlushOffset = None,
// labelFont = None,
// labelFontSize = None,
// labelFontStyle = None,
// labelFontWeight = None,
// labelLimit = None,
// labelLineHeight = None,
// labelOffset = None,
// labelOpacity = None,
// labelOverlap = None,
// labelPadding = None,
// labels = None,
// labelSeparation = None,
// maxExtent = None,
// minExtent = None,
// offset = None,
// orient = top,
// position = None,
// scale = "xscale",
// tickBand = None,
// ...
val parsedU : ujson.Value = ujson.read(copyPastedAxisFromExample)
// parsedU: Value = Obj(
// value = Map("orient" -> Str(value = "top"), "scale" -> Str(value = "xscale"))
// )
You could then use either strategy to insert into a spec, depending on whether you're starting from the "mutable" or "typesafe" approach.
import viz.vega.plots.{BarChart, given}
val copyPastedAxisFromExample = """{ "orient": "top", "scale": "xscale" }"""
val parsedU = ujson.read(copyPastedAxisFromExample)
val chart = BarChart(
List(
viz.Utils.fillDiv,
spec => spec("axes") = parsedU // overwrites the entire exes property with our single weird top axis.
)
)
viz.js.showChartJs(chart, node)
Discussion
This is my clearly favoured strategy. Start with a chart which works, construct "typesafe" modifier for the parts I want to change, and apply them to the chart. I have found this to be effective.