Visualize.Chart.Compiled (Visualize v0.2.25)

Copy Markdown View Source

A design compiled for rendering per tick (spec/14 §12, D-66).

Visualize.Chart.compile/2 is the only constructor. The frame's drawing order is a list of regions — the grid, each mark, each axis, the legend, the labels, or a facet's panels — and each is static, drawn once here in :css mode and byte for byte the same on every tick, or dynamic, drawn per tick by its closure over the frame realised for the tick's sources: a mark is always dynamic, a facet's panels are, and anything else is dynamic exactly when it reads a scale whose domain moves — inferred (:auto) or windowed by a viewport. regions/1 and dynamic_regions/1 report the split and why.

The render target of each mark is decided once, at compilation, from the rows it draws over the binding (targets/1): :svg at or below the ceiling, the binary path above it. render/2 is the hybrid chart map of Visualize.Backend.Hybrid with dynamic_svg added for the SVG layer's moving part and backdrop for the layer beneath the canvas; static/1 is the static SVG string and backdrop/1 the static backdrop; tick/3 is the per-tick payload, and for a design with a viewport it slides the canvas layer through Visualize.Incremental, whose state the struct carries. svg/2 is the one-string form, the frame's own group, equal to Visualize.Chart.Frame.render/2 when every mark targets :svg.

A :force step is warm here (spec/14 §12.5, D-132): the struct carries each graph's last layout, every node's position and velocity by id, and tick/3 and step/3 move the graph on from it — reheated to the step's alpha for at most its ticks iterations — where Visualize.Chart.render/2 lays it out cold every time. The marks of one graph share that one run, and carry/2 carries the layout across a recompilation.

Examples

iex> design = %{
...>   version: 2,
...>   sources: %{primary: %{fields: [:t, :v]}},
...>   frames: %{main: %{
...>     kind: :cartesian,
...>     scales: %{x: %{kind: :linear, domain: [0, 10]}, y: %{kind: :linear}},
...>     axes: [%{scale: :x, side: :bottom}, %{scale: :y, side: :left, grid: true}]
...>   }},
...>   marks: [%{type: :line, data: :primary, channels: %{x: :t, y: :v}}]
...> }
iex> {:ok, %{main: compiled}} = Visualize.Chart.compile(design, sources: %{primary: %{t: [1, 2], v: [3, 9]}})
iex> Visualize.Chart.Compiled.dynamic_regions(compiled)
[{:grid, {:inferred, [:y]}}, {{:mark, 0}, :data}, {{:axis, 1}, {:inferred, [:y]}}]
iex> Visualize.Chart.Compiled.targets(compiled)
[{:svg, 2}]

Summary

Types

The warm force layouts of spec/14 §12.5 (D-132): per graph, each node's position and velocity by id.

The frame's drawing order: regions, and a centred group of regions in a polar frame.

The per-tick payload of spec/14 §12.4.

Why a region is dynamic (spec/14 §12.2).

A region of spec/14 §12.2: the grid, a mark, an axis, the legend, the labels, a facet's panels.

t()

A render target of spec/14 §12.3.

Functions

The static backdrop (spec/14 §12.3, §12.4, #476, D-120): a static :tiles mark's images as one SVG root of the shape of static/1, rendered once at compilation, or "" when there are none. A page stacks it beneath the canvas, so a dense mark drawn there sits over the basemap; the attribution is in static/1, over the canvas.

A freshly compiled chart carrying the easing of the one it replaces (spec/14 §12.5, #434).

The dynamic regions alone, {region, reason} (spec/14 §12.2).

Every region in the frame's drawing order, {region, :static | {:dynamic, reason}}.

The hybrid chart map over the sources by slot name (spec/14 §12.4): width, height, margin, static, dynamic, dynamic_style, canvas_format, dynamic_svg and backdrop, the layer beneath the canvas (a basemap's images, #476).

The static SVG string, rendered once at compilation (spec/14 §12.4).

One frame of the one-string form with the state after it (spec/14 §12.5, #360): the frame's group of svg/2 as a Visualize.IR.Element, its eased scales at their shown domains for this tick, and the compiled chart carrying the easing on. :now and :at as tick/3 takes them. A caller that wants a page-ready document wraps it in Visualize.IR.Element.root/3.

The one-string form (spec/14 §12.4): the frame's group with the static and the dynamic regions in the frame's own order, as SVG; equal to Visualize.Chart.Frame.render/2 when every mark targets :svg.

{target, points} per mark in design order (spec/14 §12.3).

As tick/3 with [] options.

The per-tick payload and the compiled chart holding the tick's state (spec/14 §12.4).

Types

forces()

@type forces() :: %{
  required(term()) => %{
    required(term()) => %{x: number(), y: number(), vx: number(), vy: number()}
  }
}

The warm force layouts of spec/14 §12.5 (D-132): per graph, each node's position and velocity by id.

layout()

@type layout() :: [region() | {:centred, [region()]}]

The frame's drawing order: regions, and a centred group of regions in a polar frame.

payload()

@type payload() :: %{
  svg: String.t(),
  backdrop: String.t(),
  canvas: String.t(),
  canvas_format: :binary_base64 | :json,
  incremental: map() | nil,
  bytes: non_neg_integer()
}

The per-tick payload of spec/14 §12.4.

reason()

@type reason() :: :data | :facet | {:inferred, [atom()]}

Why a region is dynamic (spec/14 §12.2).

region()

@type region() ::
  :grid
  | {:grid, :outer}
  | {:mark, non_neg_integer()}
  | {:axis, non_neg_integer()}
  | :legend
  | :labels
  | :panels

A region of spec/14 §12.2: the grid, a mark, an axis, the legend, the labels, a facet's panels.

t()

@type t() :: %Visualize.Chart.Compiled{
  backdrop: String.t(),
  box: %{x: number(), y: number(), width: number(), height: number()},
  ceiling: pos_integer(),
  chart: Visualize.Chart.t(),
  defs: [Visualize.IR.Element.t()],
  eased: %{required(atom()) => map()},
  forces: forces(),
  layout: layout(),
  margin: %{top: number(), right: number(), bottom: number(), left: number()},
  motions: %{required(non_neg_integer()) => map()},
  moving: %{required(non_neg_integer()) => map()},
  plot: %{width: number(), height: number()},
  regions: [map()],
  size: term(),
  static: String.t(),
  targets: [{target(), non_neg_integer()}],
  theme: Visualize.Theme.t(),
  transitions: %{required(atom()) => map()},
  viewport: map() | nil
}

target()

@type target() :: :svg | :canvas | :binary

A render target of spec/14 §12.3.

Functions

backdrop(compiled)

@spec backdrop(t()) :: String.t()

The static backdrop (spec/14 §12.3, §12.4, #476, D-120): a static :tiles mark's images as one SVG root of the shape of static/1, rendered once at compilation, or "" when there are none. A page stacks it beneath the canvas, so a dense mark drawn there sits over the basemap; the attribution is in static/1, over the canvas.

carry(previous, fresh)

@spec carry(t(), t()) :: t()

A freshly compiled chart carrying the easing of the one it replaces (spec/14 §12.5, #434).

A compiled chart's plot is part of its state, so a chart drawn at a new size is a new compilation — and a scale that was mid-transition when the size changed should go on from where it stood rather than jump back to its target. The eased scale states and the mark motions of previous are kept for the scales and marks fresh still has, and so is its warm force layout for each graph fresh still has (D-132): a caller that compiles again because a tick's marks differ keeps the graph where it stood. Anything else is dropped, since a state for a scale the design no longer eases, or for a graph it no longer draws, would never be read again.

iex> design = %{version: 2, sources: %{s: %{fields: [:t, :v]}},
...>   frames: %{main: %{kind: :cartesian, scales: %{
...>     x: %{kind: :linear, domain: [0, 10]},
...>     y: %{kind: :linear, transition: %{duration: 300}}}}},
...>   marks: [%{type: :line, data: :s, channels: %{x: :t, y: :v}}]}
iex> rows = %{s: [%{t: 1, v: 3}, %{t: 2, v: 9}]}
iex> {:ok, %{main: first}} = Visualize.Chart.compile(design, sources: rows)
iex> {_group, first} = Visualize.Chart.Compiled.step(first, rows, at: 0)
iex> {:ok, %{main: wider}} = Visualize.Chart.compile(design, sources: rows, size: {900, 400})
iex> Visualize.Chart.Compiled.carry(first, wider).eased == first.eased
true

dynamic_regions(compiled)

@spec dynamic_regions(t()) :: [{region(), reason()}]

The dynamic regions alone, {region, reason} (spec/14 §12.2).

regions(compiled)

@spec regions(t()) :: [{region(), :static | {:dynamic, reason()}}]

Every region in the frame's drawing order, {region, :static | {:dynamic, reason}}.

render(compiled, sources)

@spec render(t(), map()) :: map()

The hybrid chart map over the sources by slot name (spec/14 §12.4): width, height, margin, static, dynamic, dynamic_style, canvas_format, dynamic_svg and backdrop, the layer beneath the canvas (a basemap's images, #476).

Raises ArgumentError as Visualize.Chart.Frame.new/2 does for a value a scale cannot take.

static(compiled)

@spec static(t()) :: String.t()

The static SVG string, rendered once at compilation (spec/14 §12.4).

step(compiled, sources, opts \\ [])

@spec step(t(), map(), keyword()) :: {Visualize.IR.Element.t(), t()}

One frame of the one-string form with the state after it (spec/14 §12.5, #360): the frame's group of svg/2 as a Visualize.IR.Element, its eased scales at their shown domains for this tick, and the compiled chart carrying the easing on. :now and :at as tick/3 takes them. A caller that wants a page-ready document wraps it in Visualize.IR.Element.root/3.

svg(compiled, sources)

@spec svg(t(), map()) :: String.t()

The one-string form (spec/14 §12.4): the frame's group with the static and the dynamic regions in the frame's own order, as SVG; equal to Visualize.Chart.Frame.render/2 when every mark targets :svg.

targets(compiled)

@spec targets(t()) :: [{target(), non_neg_integer()}]

{target, points} per mark in design order (spec/14 §12.3).

tick(compiled, sources)

@spec tick(t(), map()) :: {payload(), t()}

As tick/3 with [] options.

tick(compiled, sources, opts)

@spec tick(t(), map(), keyword()) :: {payload(), t()}

The per-tick payload and the compiled chart holding the tick's state (spec/14 §12.4).

svg is the SVG layer's dynamic regions as one SVG root of the shape of static/1 ("" when there are none); backdrop the dynamic regions' backdrop — a fitted basemap's images, for the page to stack beneath the canvas (#476) — in the same shape ("" when there are none); canvas the canvas layer through Visualize.Backend.Hybrid.render_dynamic/1 ("" when there is none, or when the layer streams incrementally); incremental the canvas_incremental payload of Visualize.Incremental for a design with a viewport and a binary canvas layer (spec/14 §12.5), else nil; bytes the byte size of what the tick transmits.

Options

  • :now - the window's upper end for a viewport design; by default the newest value the marks bind to the viewport scale
  • :at - the clock in milliseconds a scale's transition eases by (spec/14 §12.5); by default System.monotonic_time(:millisecond)
  • :frame - the tick's own frame node (spec/14 §12.5, #424): when it differs from the one the chart was compiled with, the frame is re-realised at it and the static regions re-rendered, which is what a design animating through its frame needs