A chart is plain data. Fude.Spec.dump/1 gives a map with string keys
and JSON values, to keep in a :map column, a JSON file or a message;
Fude.Spec.load/1 takes it back, and load(dump(chart)) is the chart.
spec = Fude.Spec.dump(Fude.bar(x: :region, y: {:sum, :revenue}, title: "Revenue"))
#=> %{"fude" => 1, "kind" => "bar", "title" => "Revenue", "x" => "region",
# "y" => %{"field" => "revenue", "aggregate" => "sum"}}
{:ok, chart} = Fude.Spec.load(spec)A spec names its data and does not hold it; the chart gets it when it draws:
<.chart id="revenue" spec={@report.chart} data={@sales} />Fude.Spec.dump(chart, data: true) puts the data in, under
"datasets", for a file that stands alone.
The shape
A chart of one layer has its kind, channels and options at the top; a
chart of several has them in "layers". A channel is a field's name, or
a map of the keys of Fude.Channel; a constant is {"value": 1000}.
"annotations" and "facet" follow the chart's notes and its small
multiples.
Versions
Every spec carries its version ("fude": 1). A later Fude loads the
specs of every earlier version, migrating them forward one version at a
time: a saved chart keeps working. A spec of a newer version than the
Fude that loads it is refused, with that reason.
Errors
load/1 checks the whole spec and gives every error, each with where it
is and what to do:
Fude.Spec.load(%{"fude" => 1, "kind" => "bar", "x" => "region", "y" => %{"field" => "revenue", "aggregate" => "summ"}, "colour" => "channel"})
#=> {:error, [
# %{path: ["colour"], message: ~s|is not a key of a bar chart; it takes x, y, color, ...; did you mean "color"?|},
# %{path: ["y", "aggregate"], message: ~s|must be one of "count", "sum", ...; did you mean "sum"?|}
# ]}<.chart spec={...}> shows them in place of the plot, and does not
raise. A spec makes no atom of what it says: a spec from a person or an
agent is safe to load.