The realized frame of a chart: its scales, built once, and the static furniture — grid, axes, legend and labels — rendered from them (spec/14 §4, D-60).
new/2 takes a Visualize.Chart and the sources its marks are bound to, realizes every
:scale node through the existing scale modules — a domain: :auto inferred from the
whole column of every mark channel that names the scale, a range: :auto from the plot
area by the scale's name — and caches the structs on the frame. scales/1 reads them,
the escape hatch for annotations, brush conversion and custom marks; put_scale/3
replaces one. generate/2 is the furniture as one Visualize.IR.Element group with
the margins applied and every theme slot resolved through Visualize.Theme.resolve/3;
render/2 is the same as a string. Given sources:, both also draw every mark of the
design between the grid and the axes through Visualize.Chart.Mark.generate/3
(spec/14 §5.6); without them a frame renders the furniture alone, over its unit
domains, which is what the static split of a compiled chart needs (spec/14 §12).
Examples
iex> chart =
...> Visualize.Chart.from_map!(%{
...> version: 2,
...> sources: %{primary: %{fields: [:t, :v]}},
...> frames: %{main: %{
...> kind: :cartesian,
...> scales: %{x: %{kind: :linear}, y: %{kind: :linear, domain: [0, :auto]}},
...> axes: [%{scale: :x, side: :bottom}, %{scale: :y, side: :left, grid: true}]
...> }},
...> marks: [%{type: :line, data: :primary, channels: %{x: :t, y: :v}}]
...> })
iex> frame = Visualize.Chart.Frame.new(chart, sources: %{primary: %{t: [1, 2, 3], v: [4, 9, 2]}}, size: {300, 200})
iex> Visualize.Chart.Frame.scale(frame, :x).domain
[1, 3]
iex> Visualize.Chart.Frame.scale(frame, :y).domain
[0, 9]
iex> frame.plot
%{width: 240, height: 150}
Summary
Types
A frame kind of spec/14 §4.1.
Whether a frame's legend is drawn (spec/14 §4.5, D-231); nil with no legend.
Why a legend is omitted (spec/14 §4.5, D-223, D-231).
Functions
A frame node's box as fractions of the render's size (spec/14 §4.2): its box, or the
share of the design's layout its cell and span take (§2.8). This is the one place
a cell resolves; new/2 reads it, and so does a tool that must know where a frame is
drawn without realizing it (§18.16). size is {width, height} or %{width, height},
which the gap is taken out of. Raises ArgumentError for a malformed box or cell, as
new/2 does.
The attributes of the design's brush for the frame it brushes (spec/14 §2.9, D-143):
data-vis-brush, its kind; data-vis-brush-x, the x mapping exactly as
sync_attrs/1 gives it; and data-vis-brush-extent, x0,y0,x1,y1, the plot area in
the chart's coordinates — what BrushHook captures over and turns into a window in
domain units (spec/10 §6.5). %{} for a frame the design does not brush.
The size a frame is realized at when no size: is given (spec/14 §4.2).
As generate/2 with resolve: :css.
The frame as one Visualize.IR.Element group (spec/14 §4.7): the static furniture,
and every mark of the design between the grid and the axes when sources are given.
The box the frame's legend is laid out in, %{width: w, height: h} in user units, not
counting the 8 between it and the plot (spec/14 §4.5, D-169): the widest row and
(rows − 1) × 16 + 12 for a top or bottom legend, the estimated width and
(entries − 1) × 16 + 12 for a stacked one, the title counted as an entry. A frame with
no legend is %{width: 0, height: 0}. A host laying out around a chart asks it rather
than repeating the text estimate.
Whether the frame's legend is drawn (spec/14 §4.5, D-231): nil when the frame has no
legend, %{status: :drawn, entries: n}, or %{status: :omitted, reason: reason, entries: n} when it is not drawn and takes no margin. n counts its realized entries,
the title not among them. The reason is the first that holds of
The frame of a chart, every scale realized and the theme resolved (spec/14 §4.7).
The projection of a :geo frame (spec/14 §4.6), nil for any other kind.
The frame with the realized scale replaced: the override of spec/14 §4.3.
As render/2 with [] options.
generate/2 rendered through Visualize.Render.to_string/2.
One realized scale; ArgumentError when the frame declares no such scale.
The realized scales by name: the escape hatch (spec/14 §4.3).
The attributes of a sync group for the frame it reads (spec/14 §2.9, D-116):
data-vis-sync, the group, and data-vis-sync-x, d0,d1,x0,x1,width — the x
scale's domain (milliseconds since the Unix epoch for a time scale), the chart pixels of
its ends and the chart's width — which is all the hooks need to map a shared x through
this frame's own scale. %{} for a frame that is no group's.
Types
@type kind() :: :cartesian | :polar | :geo | :facet
A frame kind of spec/14 §4.1.
@type legend_fit() :: %{status: :drawn, entries: non_neg_integer()} | %{status: :omitted, reason: omission(), entries: non_neg_integer()} | nil
Whether a frame's legend is drawn (spec/14 §4.5, D-231); nil with no legend.
@type omission() :: :min_entries | :cell | :row | :height | :width
Why a legend is omitted (spec/14 §4.5, D-223, D-231).
@type t() :: %Visualize.Chart.Frame{ axes: [map()], background: term() | nil, box: [number()], brush: :x | nil, clip: boolean(), defaults: [map() | nil], defs: %{required(String.t() | {:inline, String.t()}) => map()}, design_styles: %{required(atom()) => map()}, domains: %{required(atom()) => term()}, facet: %{by: atom(), columns: pos_integer()} | nil, forces: map(), generated: struct() | nil, kind: kind(), labels: [map()], legend: map() | nil, margin: %{top: number(), right: number(), bottom: number(), left: number()}, marks: [map()], name: atom() | nil, now: term() | nil, panels: [term()], paths: %{marks: [non_neg_integer()], labels: [non_neg_integer()]}, plot: %{width: number(), height: number()}, projection: Visualize.Geo.Projection.t() | nil, scales: %{required(atom()) => struct()}, schemas: %{required(atom()) => Visualize.Data.Schema.t()}, size: %{width: number(), height: number()}, styles: %{required(atom()) => map()}, sync: String.t() | nil, system: Visualize.Quantity.system(), theme: Visualize.Theme.t(), z: integer() }
Functions
A frame node's box as fractions of the render's size (spec/14 §4.2): its box, or the
share of the design's layout its cell and span take (§2.8). This is the one place
a cell resolves; new/2 reads it, and so does a tool that must know where a frame is
drawn without realizing it (§18.16). size is {width, height} or %{width, height},
which the gap is taken out of. Raises ArgumentError for a malformed box or cell, as
new/2 does.
iex> Visualize.Chart.Frame.box_of(%{box: [0.5, 0, 0.5, 1]}, nil, {600, 400})
[0.5, 0, 0.5, 1]
iex> Visualize.Chart.Frame.box_of(%{cell: [1, 0]}, %{columns: [3, 1]}, {600, 400})
[0.75, 0.0, 0.25, 1.0]
The attributes of the design's brush for the frame it brushes (spec/14 §2.9, D-143):
data-vis-brush, its kind; data-vis-brush-x, the x mapping exactly as
sync_attrs/1 gives it; and data-vis-brush-extent, x0,y0,x1,y1, the plot area in
the chart's coordinates — what BrushHook captures over and turns into a window in
domain units (spec/10 §6.5). %{} for a frame the design does not brush.
iex> chart = Visualize.Chart.from_map!(%{version: 2,
...> interaction: %{brush: :x},
...> frames: %{main: %{kind: :cartesian, scales: %{x: %{kind: :linear, domain: [0, 100]}}}}})
iex> Visualize.Chart.Frame.new(chart, size: {300, 200}) |> Visualize.Chart.Frame.brush_attrs()
%{"data-vis-brush" => "x", "data-vis-brush-extent" => "40,20,280,170", "data-vis-brush-x" => "0,100,40,280,300"}
The size a frame is realized at when no size: is given (spec/14 §4.2).
@spec generate(t()) :: Visualize.IR.Element.t()
As generate/2 with resolve: :css.
@spec generate(t(), keyword()) :: Visualize.IR.Element.t()
The frame as one Visualize.IR.Element group (spec/14 §4.7): the static furniture,
and every mark of the design between the grid and the axes when sources are given.
Options
:resolve- how the theme's slots render::css(default) for references a stylesheet can override,:literalfor the plain values a canvas needs:sources- a map from source name to anythingVisualize.Data.Table.rows/1reads, asnew/2takes; when given, the marks are drawn from them (spec/14 §5.6):paths-truefor every design node's element to carrydata-nodewith the node's path asVisualize.Chart.Validator.format_path/1spells it —frame,marks[0],frames.main.axes[0],frames.main.legend,labels[0]— for a caller that must name what it drew, as the builder's graph does (spec/14 §4.7, D-105);false(default) leaves the markup as it is:placements- the placement of the chart this frame is one of, asVisualize.Chart.placements/1returns it: a cartesian frame's marks read their rows and pixels from it rather than placing them again (#811)
Examples
iex> chart = Visualize.Chart.from_map!(%{version: 2, sources: %{s: %{fields: [:x, :y]}},
...> frames: %{main: %{kind: :cartesian, scales: %{x: %{kind: :linear}, y: %{kind: :linear}}}},
...> marks: [%{type: :line, data: :s, channels: %{x: :x, y: :y}}]})
iex> rows = [%{x: 0, y: 1}, %{x: 1, y: 2}]
iex> group = Visualize.Chart.Frame.new(chart, sources: %{s: rows}) |> Visualize.Chart.Frame.generate(sources: %{s: rows}, paths: true)
iex> {group.attrs["data-node"], Enum.map(group.children, & &1.attrs["data-node"])}
{"frames.main", ["marks[0]"]}
The box the frame's legend is laid out in, %{width: w, height: h} in user units, not
counting the 8 between it and the plot (spec/14 §4.5, D-169): the widest row and
(rows − 1) × 16 + 12 for a top or bottom legend, the estimated width and
(entries − 1) × 16 + 12 for a stacked one, the title counted as an entry. A frame with
no legend is %{width: 0, height: 0}. A host laying out around a chart asks it rather
than repeating the text estimate.
@spec legend_fit(t()) :: legend_fit()
Whether the frame's legend is drawn (spec/14 §4.5, D-231): nil when the frame has no
legend, %{status: :drawn, entries: n}, or %{status: :omitted, reason: reason, entries: n} when it is not drawn and takes no margin. n counts its realized entries,
the title not among them. The reason is the first that holds of:
:min_entries- fewer entries than itsmin_entries(D-223):cell- wrapped, an entry or the title is wider than the plot on its own:row- withwrap: false, its one row is wider than the plot:height-:topor:bottominside: false, the margin its rows need would leave the plot no height (D-222):width- at a side or a cornerinside: false, its margin is narrower than 8 and its estimated width
A host reads it to offer an omitted legend's entries another way: from
Visualize.Chart.Applied's frames after Visualize.Chart.apply/2, and as a
compiled chart's Visualize.Chart.Compiled.legend_fit/1 after a tick.
Examples
iex> chart = Visualize.Chart.from_map!(%{version: 2, frames: %{main: %{kind: :cartesian,
...> margin: %{top: 20, right: 30, bottom: 20, left: 20},
...> scales: %{color: %{kind: :ordinal, domain: ["a-long-host-name", "b"]}},
...> legend: %{scale: :color, position: :right, inside: false}}}})
iex> Visualize.Chart.Frame.new(chart) |> Visualize.Chart.Frame.legend_fit()
%{status: :omitted, reason: :width, entries: 2}
iex> Visualize.Chart.Frame.new(chart) |> Visualize.Chart.Frame.legend_extent()
%{width: 0, height: 0}
@spec new(Visualize.Chart.t(), keyword()) :: t()
The frame of a chart, every scale realized and the theme resolved (spec/14 §4.7).
Options
:sources- a map from source name to anythingVisualize.Data.Table.rows/1reads; the columns:autodomains are inferred from (default%{}):theme- a%Visualize.Theme{}; taken for any theme name the design gives, and required for one that is neither built-in nor inline:size- the{width, height}the frame is realized at (default{600, 400}, spec/14 §4.2); a design records none:now- the instant a:windowstep measures its durations back from (spec/14 §5.4.1); without it each such step takes the newest reading of its own column:schemas- the schema of each slot by name, whose declared identity a:windowstep'slatestreads (spec/14 §5.4.1, D-168);Visualize.Chart.apply/2passes the applied chart's (default%{}):system- the unit system every axis shows its measure in,:metric,:us,:ukor:as_posted(default): the viewer's, never the design's (spec/14 §4.4, D-156):warm- the force layouts a compiled chart carries between ticks (spec/14 §12.5, D-132); every:forcestep is cold without it (default%{}):generated- the generators the frame calls, their registry, the design's variables and the host's assigns (spec/14 §3.13), asVisualize.Chart.apply/2builds them; without it a generator is looked up inconfig :visualize, generators:alone
Each distinct :force step of the frame's marks is laid out once, before any domain is
inferred, and held in forces: every pipeline that reaches the step reads that layout
(spec/14 §5.4.3).
Raises ArgumentError for an unresolved variable, a theme name it cannot resolve, a
system: not of Visualize.Quantity.systems/0, a bound value a scale cannot take, or
a legend's max_label or min_entries below 1 (spec/14 §4.5). A top or bottom legend
in a margin too small for its rows grows that margin, and the plot is that much shorter
(D-169). A legend that cannot be laid out in its room — a side margin narrower than it,
a row or a single cell wider than the plot, a top or bottom margin that would leave the
plot no height — is omitted, as one below its min_entries is: not drawn and no margin
taken; legend_fit/1 says why (D-223, D-231).
@spec projection(t()) :: Visualize.Geo.Projection.t() | nil
The projection of a :geo frame (spec/14 §4.6), nil for any other kind.
The frame with the realized scale replaced: the override of spec/14 §4.3.
As render/2 with [] options.
generate/2 rendered through Visualize.Render.to_string/2.
Options
:backend-:svg,:canvas, or a module; defaults to the configured backend:resolve- the theme mode ofgenerate/2; defaults toVisualize.Theme.mode/1of the backend:sourcesand:paths- asgenerate/2takes them
One realized scale; ArgumentError when the frame declares no such scale.
The realized scales by name: the escape hatch (spec/14 §4.3).
The attributes of a sync group for the frame it reads (spec/14 §2.9, D-116):
data-vis-sync, the group, and data-vis-sync-x, d0,d1,x0,x1,width — the x
scale's domain (milliseconds since the Unix epoch for a time scale), the chart pixels of
its ends and the chart's width — which is all the hooks need to map a shared x through
this frame's own scale. %{} for a frame that is no group's.
iex> chart = Visualize.Chart.from_map!(%{version: 2,
...> interaction: %{sync: "dash"},
...> frames: %{main: %{kind: :cartesian, scales: %{x: %{kind: :linear, domain: [0, 100]}}}}})
iex> Visualize.Chart.Frame.new(chart, size: {300, 200}) |> Visualize.Chart.Frame.sync_attrs()
%{"data-vis-sync" => "dash", "data-vis-sync-x" => "0,100,40,280,300"}