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

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.

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.

Functions

A component from the word its row carries, or :error.

The palette's rows in the order shown: the four components, then a mark of every type.

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.

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

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.

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.

Types

component()

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

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

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.

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 four components, then a mark of every type.

iex> Visualize.Chart.Builder.Place.components() |> Enum.take(5)
[axis: "axis", legend: "legend", title: "title", label: "label", "mark:line": "mark: line"]

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

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()}, 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]}.

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

node(arg1, region, design, point)

@spec node(component(), region(), map(), {number(), number()}) ::
  {:sites, [site()]} | {: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.

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