Style resolution for the declarative layer (spec/14 §3.4, D-63): one style node, two outputs by the mode of the render target.
resolve/3 turns a :style node into the style map of a Visualize.IR.Element: the
fourteen element keys, every theme slot resolved through Visualize.Theme.resolve/3 —
a CSS custom-property reference in :css, the literal a canvas needs in :literal —
a dash-array list joined, a literal passed through. A {:field, f} survives it, and
bind/4 then reads it from a datum, a color key's reading through the frame's color
scale when the frame declares one. The frame's furniture and the marks share this
resolver.
A style is stored in compound parts (spec/14 §3.1, D-172) — fill, stroke, font,
text, effect, marker, curve — and normalize/1 turns a shorthand or the flat keys
of an earlier version into them. The element keys are the flat ones they always were:
resolve/3 projects the parts onto them, so the IR and the backends read what they read.
A style is looked up by name with its extends chain flattened first — the parent's parts,
the child's merged over them by merge/2 (spec/14 §3.5, D-80, D-172) — so no consumer
sees that key and a resolved node never carries it. A cycle raises ArgumentError; the
validator reports it by path.
Examples
iex> theme = Visualize.Theme.default()
iex> Visualize.Chart.Style.resolve(%{stroke: %{color: :axis, width: 2}, curve: :step}, theme, :css)
%{stroke: "var(--vis-axis, #666666)", stroke_width: 2}
iex> Visualize.Chart.Style.resolve(%{stroke: :axis, stroke_width: 2, curve: :step}, theme, :css)
%{stroke: "var(--vis-axis, #666666)", stroke_width: 2}
iex> Visualize.Chart.Style.resolve(%{stroke: :axis, stroke_dasharray: [2, 2]}, theme, :literal)
%{stroke: "#666666", stroke_dasharray: "2 2"}
Summary
Functions
The map of resolve/3 with every {:field, f} read from the datum (spec/14 §3.4).
A projected style node with the keys of the parts a mark of type does not draw taken
out (spec/14 §5.6, D-172): a line and a rule draw the stroke, the closed kinds the fill
and an outline, a symbol its marker too, a text and a label the font and the text
placement. A part a kind does not draw is ignored, never an error: styles are shared.
over laid on under (spec/14 §3.5, D-172): each part merges key by key, so
%{stroke: %{width: 3}} over a dashed red stroke is a dashed red stroke 3 wide — but a
part whose type changes is replaced, keeping only what no type decides (a paint's
opacity, a stroke's width, dash, join, cap and placement).
A style node in the shape it is stored in (spec/14 §3.1, D-172): the flat keys of an
earlier version moved into their parts, and every shorthand expanded — fill: :red is a
solid paint, curve: :step a curve of that type, a gradient's color list its stops.
A style's parts written as the flat keys an element and a mark's generator read (spec/14
§3.4, D-172): fill.color is fill, stroke.width is stroke_width, font.size is
font_size, a curve's type is curve and its tension and alpha curve_opts. A
gradient written inline is {:paint, name}, drawn from the frame's defs. Keys
already flat pass through, so projecting twice is projecting once.
The element style map of a style node, every slot resolved in mode (spec/14 §3.4).
The stack with a view-state entry laid at its layer (spec/14 §3.7, D-172): after its names and field entries and before a trailing inline node — the site's local edit — so the state beats the data's choice and the local edit beats the state.
Functions
@spec bind(map(), term(), map(), Visualize.Theme.mode()) :: map()
The map of resolve/3 with every {:field, f} read from the datum (spec/14 §3.4).
A color key's reading passes through the frame's color scale when the frame
declares one — a category outside its domain is the scale's unknown — and is
resolved as a theme slot when it is one. A color is never dropped: a nil reading,
or a scale that yields nil, is :none, the explicit "no paint" SVG and canvas agree
on, where an absent fill is black on one and nothing on the other (D-122). Any other
key's nil reading drops the key.
A projected style node with the keys of the parts a mark of type does not draw taken
out (spec/14 §5.6, D-172): a line and a rule draw the stroke, the closed kinds the fill
and an outline, a symbol its marker too, a text and a label the font and the text
placement. A part a kind does not draw is ignored, never an error: styles are shared.
iex> node = %{stroke: :bad, stroke_width: 2, fill: :bad, symbol: :cross}
iex> Visualize.Chart.Style.drawn(node, :line)
%{stroke: :bad, stroke_width: 2}
iex> Visualize.Chart.Style.drawn(node, :rect)
%{stroke: :bad, stroke_width: 2, fill: :bad}
over laid on under (spec/14 §3.5, D-172): each part merges key by key, so
%{stroke: %{width: 3}} over a dashed red stroke is a dashed red stroke 3 wide — but a
part whose type changes is replaced, keeping only what no type decides (a paint's
opacity, a stroke's width, dash, join, cap and placement).
iex> under = Visualize.Chart.Style.normalize(%{stroke: :bad, stroke_dasharray: "4 2"})
iex> Visualize.Chart.Style.merge(under, %{stroke: %{width: 3}})
%{stroke: %{type: :solid, color: :bad, dash: "4 2", width: 3}}
iex> Visualize.Chart.Style.merge(%{fill: %{type: :solid, color: :red, opacity: 0.5}}, %{fill: %{type: :linear, stops: []}})
%{fill: %{type: :linear, stops: [], opacity: 0.5}}
A style node in the shape it is stored in (spec/14 §3.1, D-172): the flat keys of an
earlier version moved into their parts, and every shorthand expanded — fill: :red is a
solid paint, curve: :step a curve of that type, a gradient's color list its stops.
iex> Visualize.Chart.Style.normalize(%{stroke: :axis, stroke_width: 2})
%{stroke: %{type: :solid, color: :axis, width: 2}}
iex> Visualize.Chart.Style.normalize(%{fill: %{type: :linear, color: ["#fff", "#000"]}})
%{fill: %{type: :linear, stops: [%{at: 0, color: "#fff"}, %{at: 1, color: "#000"}]}}
A style's parts written as the flat keys an element and a mark's generator read (spec/14
§3.4, D-172): fill.color is fill, stroke.width is stroke_width, font.size is
font_size, a curve's type is curve and its tension and alpha curve_opts. A
gradient written inline is {:paint, name}, drawn from the frame's defs. Keys
already flat pass through, so projecting twice is projecting once.
iex> Visualize.Chart.Style.project(%{stroke: %{type: :solid, color: :axis, width: 2, dash: [2, 2]}, font: %{size: 12}})
%{stroke: :axis, stroke_width: 2, stroke_dasharray: [2, 2], font_size: 12}
@spec resolve(map(), Visualize.Theme.t(), Visualize.Theme.mode()) :: map()
The element style map of a style node, every slot resolved in mode (spec/14 §3.4).
Only the keys an element renders are kept; curve, curve_opts, symbol and
symbol_size reach the mark's generator instead. A {:field, f} is kept for bind/4.
Raises ArgumentError for a variable: variables resolve at application (spec/14 §7.3).
The stack with a view-state entry laid at its layer (spec/14 §3.7, D-172): after its names and field entries and before a trailing inline node — the site's local edit — so the state beats the data's choice and the local edit beats the state.
iex> Visualize.Chart.Style.with_state([:series, %{stroke: %{width: 5}}], {:field, :state})
[:series, {:field, :state}, %{stroke: %{width: 5}}]
iex> Visualize.Chart.Style.with_state(:series, {:field, :state})
[:series, {:field, :state}]
iex> Visualize.Chart.Style.with_state(nil, {:field, :state})
[{:field, :state}]