A fragment whose content is a list of use sites, and how it becomes a design (spec/14 §19.6, D-103).
%{composite: %{
uses: [%Visualize.Chart.Use{...}, ...], # in stack order, the higher later
vars: %{color_1: %{default: :series_1}} # the composite's own variables
}}Save writes structure: this is what the store holds. Flatten is a deployment
verb: flatten/2 resolves every use through a store — ref → local → mask → bind
— recursively where a use refers to another composite, and stacks the result into a flat
design in §13's shape, with no ids and no uses. A composite that reaches itself through
any chain of uses is refused as an extends cycle is (§3.5).
A ref the store no longer holds is a mask of everything at that site (§19.2); the design still flattens, and the missing ids are returned so a host that wants strictness has them.
Every site is placed by its kind (§19.6): a positional kind writes its key, a frame
cascades into frame, a list kind appends by its own identity, and a name-keyed kind —
style, scale, source, var — lands under the site's key. A name-keyed site with no key
is not placed, and is reported as keyless beside missing.
Summary
Functions
Whether a fragment is a composite: one key, composite, holding uses.
A flat document as sites, one kind each (spec/14 §18.12): its theme, its meta, its frame
without the scales and axes, then one keyed site per style, scale, source and var, one
site per mark, per label and per axis, in the order the document is read. flatten/2 of
the result is the document again, version aside, which flatten writes. Each site comes
with a label a person reads.
A composite resolved through a store and stacked: a flat design (spec/14 §19.6).
A site's contribution placed in the design by its kind (spec/14 §19.6): a wrapper body
becomes the layer that declares it where the design keeps that kind — a name-keyed one
under the site's key — and anything else cascades as it is.
Types
@type report() :: %{ missing: [ {Visualize.Chart.Use.id() | [atom() | non_neg_integer()], Visualize.Chart.Fragment.id()} ], keyless: [Visualize.Chart.Use.id()] }
What flatten/2 reports beside the design: every ref no store could find — a site's,
by the site's id, or a reference inside the design, by its path — and every name-keyed
site this composite holds that has no key to be declared under.
Functions
Whether a fragment is a composite: one key, composite, holding uses.
@spec decompose(map()) :: {[Visualize.Chart.Use.t()], %{required(Visualize.Chart.Use.id()) => String.t()}}
A flat document as sites, one kind each (spec/14 §18.12): its theme, its meta, its frame
without the scales and axes, then one keyed site per style, scale, source and var, one
site per mark, per label and per axis, in the order the document is read. flatten/2 of
the result is the document again, version aside, which flatten writes. Each site comes
with a label a person reads.
iex> document = %{version: 2, theme: :dark, styles: %{series: %{stroke: "#111"}},
...> frames: %{main: %{kind: :cartesian, scales: %{x: %{kind: :linear}}, axes: [%{scale: :x, side: :bottom}]}},
...> marks: [%{id: :m, type: :line}]}
iex> {uses, labels} = Visualize.Chart.Composite.decompose(document)
iex> Enum.map(uses, &{&1.id, Map.keys(&1.local), &1.key})
[{{:use, 1}, [:theme], nil}, {{:use, 2}, [:frames], nil}, {{:use, 3}, [:style], :series}, {{:use, 4}, [:scale], :x}, {{:use, 5}, [:axis], nil}, {{:use, 6}, [:mark], nil}]
iex> labels[{:use, 3}]
"style series"
@spec flatten(map(), (Visualize.Chart.Fragment.id() -> {:ok, map()} | :error)) :: {:ok, map(), report()} | {:error, {:cycle, [Visualize.Chart.Fragment.id()]}}
A composite resolved through a store and stacked: a flat design (spec/14 §19.6).
fetch returns {:ok, fragment} or :error for an id — a store's get, partially
applied. The composite's own vars are the base layer, so a variable a use was renamed
to (§19.4) has its declaration; each use resolves in order and cascades by §8.2.
Returns {:ok, design, report}, or {:error, {:cycle, [id]}} when a composite reaches
itself, naming the chain.
@spec place(map(), Visualize.Chart.Use.t()) :: {:ok, map()} | :keyless
A site's contribution placed in the design by its kind (spec/14 §19.6): a wrapper body
becomes the layer that declares it where the design keeps that kind — a name-keyed one
under the site's key — and anything else cascades as it is.
iex> Visualize.Chart.Composite.place(%{style: %{stroke_width: 5}}, %Visualize.Chart.Use{id: {:use, 1}, ref: nil, key: :series})
{:ok, %{styles: %{series: %{stroke_width: 5}}}}
iex> Visualize.Chart.Composite.place(%{mark: %{id: :m, type: :line}}, %Visualize.Chart.Use{id: {:use, 1}, ref: nil})
{:ok, %{marks: [%{id: :m, type: :line}]}}
iex> Visualize.Chart.Composite.place(%{scale: %{kind: :linear}}, %Visualize.Chart.Use{id: {:use, 1}, ref: nil})
:keyless
iex> Visualize.Chart.Composite.place(%{theme: :dark, frames: %{main: %{}}}, %Visualize.Chart.Use{id: {:use, 1}, ref: nil})
{:ok, %{theme: :dark, frames: %{main: %{}}}}
iex> composite = %{frames: %{main: %{kind: :polar}}, marks: [%{type: :arc, data: :d}]}
iex> Visualize.Chart.Composite.place(composite, %Visualize.Chart.Use{id: {:use, 1}, ref: nil, key: :gauge})
{:ok, %{frames: %{gauge: %{kind: :polar}}, marks: [%{type: :arc, data: :d, frame: :gauge}]}}