Where a drop on the graph lands, and what a component means there (spec/14 §18.16).
A drop is a point in chart coordinates; region/2 classifies it against the frame the
preview drew — the plot area, or one of the four margins — and node/4 says what the
dropped component becomes in that region: an axis on that side with the scale it needs,
a title or a label at the anchor the region implies, a legend at the nearest position, a
mark of a type. home/1 is the region the keyboard twin and the + menu place in.
Every function here is pure; the builder inserts what node/4 returns.
This module compiles only when Phoenix.Component is loaded (spec/10 §1.1, D-45, D-85).
Summary
Types
What the palette offers: a component, or a mark of a type.
Where a frame is drawn in the graph: its pixel box, its size and its margin.
A region of the frame a drop lands in.
A site to insert: its kind, its body under the kind's key, and its key if named.
A point on the graph read in the frame under it (spec/14 §18.16).
Functions
A component from the word its row carries, or :error.
The palette's rows in the order shown: the five components — a frame among them (#616) — then a mark of every type.
Every frame of a design as the graph draws it at size (spec/14 §18.4, §18.16): its
name, its pixel box (x, y, size) and its margin, in drawing order — z, then
name, so the last is the one on top (§4.7). The box is Visualize.Chart.Frame.box_of/3
of the frame, its cell resolved over the design's layout; a frame whose placement does
not read yet is drawn over the whole box, as it would be with none. A design with no
frame is one frame named as Visualize.Chart.Fragment.frame_name/1 names it.
The size and margin the preview draws a design at: the size is the render's own
default, since a design records none (spec/14 §4.2), and the margin is the design's
over the schema's default.
The design's layout as the graph draws it at a size (spec/14 §2.8, §18.16, #617): each
column's {x, w} and each row's {y, h} as fractions, the gap taken out of the cells
as Visualize.Chart.Frame.box_of/3 takes it; nil for a design with no layout, or one
whose weights do not read.
A mark type's data and channels guessed over the composed design (spec/14 §18.16):
the chart's first source — the one the last mark binds, else the first declared by
name — and its fields in order over the type's required channels; fewer fields leave
the rest unbound; no source, nothing.
The region a component is placed in from the keyboard or the + menu: an axis at the
bottom, a title at the top, a label at the bottom, a legend and a mark in the plot.
Whether a drawn node's label names a node that is moved on the graph (spec/14 §18.16): an axis, the legend, a label. A mark's position is its data; the frame does not move.
A node moved to a region at a point over the composed design (spec/14 §18.16): its body
with the placement key rewritten — an axis's side, a legend's position, a label's
anchor — or :same when the region names what it already is, or :unmovable for a
kind with no placement. An axis whose orientation changes takes the scale its new side
reads — the one another axis on that orientation reads, else x or y — since a
scale's range is set by its name (§4.3); a scale the design lacks is brought as a drop
brings it, {:ok, body, [scale_site]}. The point is a spot/3 or a bare point, as
node/4 reads one.
A new frame dropped on the graph at a point (spec/14 §18.16, #616): a fresh name —
main in a design with none, else the first free frame_<n> from 2 — and a box a
quarter of the graph, half its width and half its height, centered on the point and
kept inside, as fractions rounded to four places. :center is the graph's center,
where the keyboard twin and + frame place one.
What a component becomes in a region of the composed design, dropped at a point:
{:sites, sites} — lowest first, so an axis's scale comes before the axis — or
{:legend, node} for the frame's legend, or {:error, :no_scale} for a legend on a
design with no ordinal scale. The point is a spot/3 — read in the frame under it —
or a bare point, read in the design's frame at the render's default size.
A library entry's body placed: an axis's side, a label's anchor set from the region
where the region names one; anything else, and a drop in the plot, as the entry wrote it.
The region a point in chart coordinates falls in: inside the plot area is :plot;
otherwise the top or bottom band, then the left or right — so a corner is the top's or
the bottom's, where the title and the x axis live.
Whether a design has several frames (spec/14 §2.1), so that what is placed on the graph says which one it belongs to (#615).
A point on the graph, in chart coordinates, read in the frame under it (spec/14
§18.16): the topmost frame whose box holds the point — else the nearest — with the
point in that frame's own coordinates and the frame's size and margin, which region/2
classifies.
A spot in a named frame, at its origin (#615): what the keyboard twin places with,
which names a region and no point — the open node's frame, at the graph's size. A
frame the design does not draw is read as spot/3 reads the origin.
Types
@type component() :: :axis | :legend | :title | :label | :frame | {:mark, atom()}
What the palette offers: a component, or a mark of a type.
@type frame_geometry() :: %{ name: atom(), x: number(), y: number(), size: %{width: number(), height: number()}, margin: map() }
Where a frame is drawn in the graph: its pixel box, its size and its margin.
@type region() :: :plot | :top | :bottom | :left | :right
A region of the frame a drop lands in.
A site to insert: its kind, its body under the kind's key, and its key if named.
@type spot() :: %{ frame: atom(), point: {number(), number()}, geometry: %{size: map(), margin: map()} }
A point on the graph read in the frame under it (spec/14 §18.16).
Functions
A component from the word its row carries, or :error.
iex> Visualize.Chart.Builder.Place.component("mark:line")
{:ok, {:mark, :line}}
iex> Visualize.Chart.Builder.Place.component("axis")
{:ok, :axis}
iex> Visualize.Chart.Builder.Place.component("mark:banana")
:error
The palette's rows in the order shown: the five components — a frame among them (#616) — then a mark of every type.
iex> Visualize.Chart.Builder.Place.components() |> Enum.take(6)
[axis: "axis", legend: "legend", title: "title", label: "label", frame: "frame", "mark:line": "mark: line"]
@spec frames(map(), {number(), number()}) :: [frame_geometry()]
Every frame of a design as the graph draws it at size (spec/14 §18.4, §18.16): its
name, its pixel box (x, y, size) and its margin, in drawing order — z, then
name, so the last is the one on top (§4.7). The box is Visualize.Chart.Frame.box_of/3
of the frame, its cell resolved over the design's layout; a frame whose placement does
not read yet is drawn over the whole box, as it would be with none. A design with no
frame is one frame named as Visualize.Chart.Fragment.frame_name/1 names it.
iex> design = %{frames: %{history: %{box: [0, 0, 0.5, 1]}, now: %{box: [0.5, 0, 0.5, 1], margin: %{left: 10}}}}
iex> for f <- Visualize.Chart.Builder.Place.frames(design, {800, 400}), do: {f.name, f.x, f.size.width, f.margin.left}
[{:history, 0.0, 400.0, 40}, {:now, 400.0, 400.0, 10}]
The size and margin the preview draws a design at: the size is the render's own
default, since a design records none (spec/14 §4.2), and the margin is the design's
over the schema's default.
iex> Visualize.Chart.Builder.Place.geometry(%{})
%{size: %{width: 600, height: 400}, margin: %{top: 20, right: 20, bottom: 30, left: 40}}
@spec grid(map(), {number(), number()}) :: %{columns: [{number(), number()}], rows: [{number(), number()}]} | nil
The design's layout as the graph draws it at a size (spec/14 §2.8, §18.16, #617): each
column's {x, w} and each row's {y, h} as fractions, the gap taken out of the cells
as Visualize.Chart.Frame.box_of/3 takes it; nil for a design with no layout, or one
whose weights do not read.
iex> Visualize.Chart.Builder.Place.grid(%{layout: %{columns: [3, 1]}}, {800, 400})
%{columns: [{0.0, 0.75}, {0.75, 0.25}], rows: [{0.0, 1.0}]}
iex> Visualize.Chart.Builder.Place.grid(%{}, {800, 400})
nil
A mark type's data and channels guessed over the composed design (spec/14 §18.16):
the chart's first source — the one the last mark binds, else the first declared by
name — and its fields in order over the type's required channels; fewer fields leave
the rest unbound; no source, nothing.
iex> design = %{sources: %{s: %{fields: [:hour, :value, :extra]}}}
iex> Visualize.Chart.Builder.Place.guess(design, :line)
%{data: :s, channels: %{x: :hour, y: :value}}
iex> Visualize.Chart.Builder.Place.guess(design, :rect)
%{data: :s, channels: %{x0: :hour, x1: :value, y0: :extra}}
iex> Visualize.Chart.Builder.Place.guess(%{}, :line)
%{}
The region a component is placed in from the keyboard or the + menu: an axis at the
bottom, a title at the top, a label at the bottom, a legend and a mark in the plot.
Whether a drawn node's label names a node that is moved on the graph (spec/14 §18.16): an axis, the legend, a label. A mark's position is its data; the frame does not move.
iex> Visualize.Chart.Builder.Place.movable?("frames.main.axes[1]")
true
iex> Visualize.Chart.Builder.Place.movable?("marks[0]")
false
@spec moved(atom(), map(), region(), {number(), number()} | spot(), map()) :: {:ok, map()} | {:ok, map(), [site()]} | :same | :unmovable
A node moved to a region at a point over the composed design (spec/14 §18.16): its body
with the placement key rewritten — an axis's side, a legend's position, a label's
anchor — or :same when the region names what it already is, or :unmovable for a
kind with no placement. An axis whose orientation changes takes the scale its new side
reads — the one another axis on that orientation reads, else x or y — since a
scale's range is set by its name (§4.3); a scale the design lacks is brought as a drop
brings it, {:ok, body, [scale_site]}. The point is a spot/3 or a bare point, as
node/4 reads one.
iex> Visualize.Chart.Builder.Place.moved(:axis, %{scale: :x, side: :bottom}, :top, {0, 0})
{:ok, %{scale: :x, side: :top}}
iex> Visualize.Chart.Builder.Place.moved(:axis, %{scale: :x, side: :bottom}, :left, {0, 0}, %{frames: %{"main" => %{scales: %{"x" => %{}, "y" => %{}}}}})
{:ok, %{scale: "y", side: :left}}
iex> Visualize.Chart.Builder.Place.moved(:axis, %{scale: :x, side: :bottom}, :left, {0, 0})
{:ok, %{scale: "y", side: :left}, [%{kind: :scale, body: %{kind: :linear, domain: :auto}, key: "y"}]}
iex> Visualize.Chart.Builder.Place.moved(:axis, %{scale: :x, side: :bottom}, :bottom, {0, 0})
:same
iex> Visualize.Chart.Builder.Place.moved(:mark, %{type: :line}, :top, {0, 0})
:unmovable
@spec new_frame(map(), {number(), number()}, {number(), number()} | :center) :: {String.t(), [number()]}
A new frame dropped on the graph at a point (spec/14 §18.16, #616): a fresh name —
main in a design with none, else the first free frame_<n> from 2 — and a box a
quarter of the graph, half its width and half its height, centered on the point and
kept inside, as fractions rounded to four places. :center is the graph's center,
where the keyboard twin and + frame place one.
iex> design = %{frames: %{"main" => %{}, "frame_2" => %{}}}
iex> Visualize.Chart.Builder.Place.new_frame(design, {800, 400}, {700, 100})
{"frame_3", [0.5, 0, 0.5, 0.5]}
iex> Visualize.Chart.Builder.Place.new_frame(%{}, {800, 400}, :center)
{"main", [0.25, 0.25, 0.5, 0.5]}
@spec node(component(), region(), map(), {number(), number()} | spot()) :: {:sites, [site()]} | {:frame, atom(), %{scales: map(), axes: [map()]}} | {:legend, map()} | {:error, :no_scale}
What a component becomes in a region of the composed design, dropped at a point:
{:sites, sites} — lowest first, so an axis's scale comes before the axis — or
{:legend, node} for the frame's legend, or {:error, :no_scale} for a legend on a
design with no ordinal scale. The point is a spot/3 — read in the frame under it —
or a bare point, read in the design's frame at the render's default size.
iex> design = %{frames: %{"main" => %{scales: %{"x" => %{kind: :linear}}}}}
iex> Visualize.Chart.Builder.Place.node(:axis, :bottom, design, {0, 0})
{:sites, [%{kind: :axis, body: %{scale: "x", side: :bottom}, key: nil}]}
iex> Visualize.Chart.Builder.Place.node(:axis, :left, design, {0, 0})
{:sites, [%{kind: :scale, body: %{kind: :linear, domain: :auto}, key: "y"}, %{kind: :axis, body: %{scale: "y", side: :left}, key: nil}]}
iex> Visualize.Chart.Builder.Place.node(:legend, :plot, design, {0, 0})
{:error, :no_scale}
A library entry's body placed: an axis's side, a label's anchor set from the region
where the region names one; anything else, and a drop in the plot, as the entry wrote it.
iex> Visualize.Chart.Builder.Place.placed(%{axis: %{scale: :y, side: :left}}, :right)
%{axis: %{scale: :y, side: :right}}
iex> Visualize.Chart.Builder.Place.placed(%{axis: %{scale: :y, side: :left}}, :plot)
%{axis: %{scale: :y, side: :left}}
The region a point in chart coordinates falls in: inside the plot area is :plot;
otherwise the top or bottom band, then the left or right — so a corner is the top's or
the bottom's, where the title and the x axis live.
iex> frame = %{size: %{width: 400, height: 200}, margin: %{top: 20, right: 20, bottom: 30, left: 40}}
iex> Visualize.Chart.Builder.Place.region({200, 100}, frame)
:plot
iex> Visualize.Chart.Builder.Place.region({200, 190}, frame)
:bottom
iex> Visualize.Chart.Builder.Place.region({5, 5}, frame)
:top
iex> Visualize.Chart.Builder.Place.region({5, 100}, frame)
:left
Whether a design has several frames (spec/14 §2.1), so that what is placed on the graph says which one it belongs to (#615).
iex> Visualize.Chart.Builder.Place.several?(%{frames: %{a: %{}, b: %{}}})
true
iex> Visualize.Chart.Builder.Place.several?(%{frames: %{main: %{}}})
false
A point on the graph, in chart coordinates, read in the frame under it (spec/14
§18.16): the topmost frame whose box holds the point — else the nearest — with the
point in that frame's own coordinates and the frame's size and margin, which region/2
classifies.
iex> design = %{frames: %{history: %{box: [0, 0, 0.5, 1]}, now: %{box: [0.5, 0, 0.5, 1]}}}
iex> spot = Visualize.Chart.Builder.Place.spot(design, {800, 400}, {500, 390})
iex> {spot.frame, spot.point, Visualize.Chart.Builder.Place.region(spot.point, spot.geometry)}
{:now, {100.0, 390.0}, :bottom}
A spot in a named frame, at its origin (#615): what the keyboard twin places with,
which names a region and no point — the open node's frame, at the graph's size. A
frame the design does not draw is read as spot/3 reads the origin.
iex> design = %{frames: %{history: %{box: [0, 0, 0.5, 1]}, now: %{box: [0.5, 0, 0.5, 1]}}}
iex> spot = Visualize.Chart.Builder.Place.spot_in(design, {800, 400}, :now)
iex> {spot.frame, spot.point, spot.geometry.size}
{:now, {0, 0}, %{width: 400.0, height: 400.0}}