Visualize.Chart.Builder.Place (Visualize v0.7.0)

Copy Markdown View Source

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

component()

@type component() :: :axis | :legend | :title | :label | :frame | {:mark, atom()}

What the palette offers: a component, or a mark of a type.

frame_geometry()

@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.

region()

@type region() :: :plot | :top | :bottom | :left | :right

A region of the frame a drop lands in.

site()

@type site() :: %{kind: atom(), body: map(), key: atom() | nil}

A site to insert: its kind, its body under the kind's key, and its key if named.

spot()

@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

component(word)

@spec component(String.t()) :: {:ok, component()} | :error

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

components()

@spec components() :: [{atom(), String.t()}]

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"]

frames(design, arg)

@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}]

geometry(design)

@spec geometry(map()) :: %{size: map(), margin: map()}

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}}

grid(design, size)

@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

guess(design, type)

@spec guess(map(), atom()) :: map()

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)
%{}

home(arg1)

@spec home(component()) :: region()

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.

movable?(arg1)

@spec movable?(String.t()) :: boolean()

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

moved(kind, body, region, point, design \\ %{})

@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

new_frame(design, arg2, arg3)

@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]}

node(arg1, region, design, point)

@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}

placed(fragment, region)

@spec placed(map(), region()) :: map()

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}}

region(arg, map)

@spec region({number(), number()}, %{size: map(), margin: map()}) :: region()

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

several?(arg1)

@spec several?(term()) :: boolean()

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

spot(design, size, arg)

@spec spot(map(), {number(), number()}, {number(), number()}) :: spot()

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}

spot_in(design, size, frame)

@spec spot_in(map(), {number(), number()}, atom()) :: spot()

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}}