Mixed typesafe / mutable

Start mutable and add a little safety

The "mutuable" approach uses ujson.Value to represent a chart. We could use a "typesafe" part of the DSL, to mutate the part we want... then turn into ujson.

import viz.dsl.vega.*
import viz.vega.plots.BarChart
import viz.dsl.Conversion.u
import viz.doc.makePlotTarget

val axisOrient : TitleOrientEnum = "top"
val newAxis : Axis= Axis(orient = axisOrient, scale = "xscale")

// for demonstration purposes
val asUjson : ujson.Value = newAxis.u
// the .u is an hacky extension method to move from circe json to ujson.Value

BarChart(
    List(
        viz.Utils.removeXAxis, 
        viz.Utils.fillDiv,
        spec => spec("axes") = spec("axes").arr :+ newAxis.u
    )
)(using makePlotTarget(node, 50))

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 = LinkedHashMap(
//     "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.