The scales a design's bound channels need and the frame lacks (spec/14 §18.16, #370).
A channel finds its scale by name and a scale the frame does not declare is the identity
(§4.3) — right for a layout that emits pixels, a trap for a person binding a column. So
whenever the builder binds a channel it asks derive/2 for the scales to declare, each
kind read from the column's type (§2.3, or what its rows suggest), and
unscaled/2 for the position channels it could not type, which are needs.
Every function here is pure; the builder writes what derive/2 returns.
This module compiles only when Phoenix.Component is loaded (spec/10 §1.1, D-45, D-85).
Summary
Types
The types the pool's rows suggest, by source name.
Functions
The type the design declares for a source's field, else the type its rows suggest.
For every mark that binds a source, the columns a channel may pick (spec/14 §18.6,
#371): the source's declared fields and the pool's columns for it, sorted, each with
its type — the design's, else the rows' — keyed by the channels node's path.
The scales the design's bound channels name and the frame does not declare, as
%{name => node}, each %{kind: kind, domain: :auto} with the kind from the channel's
evidence: time → :time, number → :linear, a category or text → :band (the layer's
one categorical position scale, spec/03); on the colour scale a category → :ordinal,
a number → :sequential. A channel with no evidence derives nothing; a scale already
declared is never in the result.
The types the pool's rows suggest, by source, over each source's first 200 rows
(Visualize.Data.Table.column_types/1); a source with no rows suggests nothing.
The position scales a mark reads — x, y, angle, r — through its channels and
its scales renames, in channel order (spec/14 §18.16, #373).
The position channels bound to a field that names a scale the frame lacks and no type
could be read for: {[:marks, i, :channels, ch], {:unscaled, field}} each, the
builder's needs (spec/14 §18.16).
Types
Functions
@spec column_type(map(), pool_types(), atom(), atom()) :: atom() | nil
The type the design declares for a source's field, else the type its rows suggest.
For every mark that binds a source, the columns a channel may pick (spec/14 §18.6,
#371): the source's declared fields and the pool's columns for it, sorted, each with
its type — the design's, else the rows' — keyed by the channels node's path.
iex> design = %{sources: %{s: %{fields: [:t, :v], types: %{t: :time}}},
...> marks: [%{type: :line, data: :s}, %{type: :line}]}
iex> Visualize.Chart.Builder.Scales.columns(design, %{s: [%{t: 1, v: 2, extra: "x"}]})
%{[:marks, 0, :channels] => [%{name: :extra, type: :text}, %{name: :t, type: :time}, %{name: :v, type: :number}]}
@spec derive(map(), pool_types()) :: %{required(atom()) => map()}
The scales the design's bound channels name and the frame does not declare, as
%{name => node}, each %{kind: kind, domain: :auto} with the kind from the channel's
evidence: time → :time, number → :linear, a category or text → :band (the layer's
one categorical position scale, spec/03); on the colour scale a category → :ordinal,
a number → :sequential. A channel with no evidence derives nothing; a scale already
declared is never in the result.
iex> design = %{frames: %{main: %{kind: :cartesian}},
...> sources: %{s: %{fields: [:t, :v, :k], types: %{t: :time, v: :number, k: :category}}},
...> marks: [%{type: :line, data: :s, channels: %{x: :t, y: :v}},
...> %{type: :rect, data: :s, channels: %{x0: :k, x1: :k, y0: 0, y1: :v, value: :k}}]}
iex> Visualize.Chart.Builder.Scales.derive(design, %{})
%{x: %{kind: :time, domain: :auto}, y: %{kind: :linear, domain: :auto}, color: %{kind: :ordinal, domain: :auto}}
iex> Visualize.Chart.Builder.Scales.derive(%{frames: %{main: %{kind: :cartesian}}}, %{})
%{}
@spec pool_types(map()) :: pool_types()
The types the pool's rows suggest, by source, over each source's first 200 rows
(Visualize.Data.Table.column_types/1); a source with no rows suggests nothing.
iex> Visualize.Chart.Builder.Scales.pool_types(%{s: [%{t: 1, k: "a"}], e: []})
%{s: %{t: :number, k: :text}, e: %{}}
The position scales a mark reads — x, y, angle, r — through its channels and
its scales renames, in channel order (spec/14 §18.16, #373).
iex> design = %{frames: %{main: %{kind: :cartesian}}}
iex> Visualize.Chart.Builder.Scales.position_scales(design, %{type: :line, channels: %{y: :v, x: :t, series: :k}})
[:x, :y]
iex> Visualize.Chart.Builder.Scales.position_scales(%{frames: %{main: %{kind: :polar}}}, %{type: :rose, channels: %{angle: :k, value: :v}})
[:angle, :r]
@spec unscaled(map(), pool_types()) :: [{[term()], {:unscaled, atom()}}]
The position channels bound to a field that names a scale the frame lacks and no type
could be read for: {[:marks, i, :channels, ch], {:unscaled, field}} each, the
builder's needs (spec/14 §18.16).
iex> design = %{frames: %{main: %{kind: :cartesian}}, sources: %{s: %{fields: [:t, :v]}},
...> marks: [%{type: :line, data: :s, channels: %{x: :t, y: :v}}]}
iex> Visualize.Chart.Builder.Scales.unscaled(design, %{})
[{[:marks, 0, :channels, :x], {:unscaled, :t}}, {[:marks, 0, :channels, :y], {:unscaled, :v}}]
iex> Visualize.Chart.Builder.Scales.unscaled(design, %{s: %{t: :number, v: :number}})
[]