The builder's fragment editor, generated from the schema (spec/14 §18.5-§18.7).
Nothing here is written by hand. nodes/1 walks a fragment by the type of every key
it carries, so which nodes a person may open is a query over
Visualize.Chart.Schema.describe/1 and not a list; widget/1 maps a
Visualize.Chart.Schema.type/0 to a control and parse/2 reads a submitted string
back as a value of that type, so a key added to the schema gets its control and a node
kind added becomes openable, with no edit here (D-86).
A composite value — a text, an extent, a whole node — is a textarea holding what
inspect/1 writes, read back by parse/2 as a literal: the string is parsed with
Code.string_to_quoted/1, never evaluated, and only the vocabulary a design may
contain is accepted (spec/14 §1.4). A design map therefore cannot acquire a function or
a term the JSON codec has no form for.
This module compiles only when Phoenix.Component is loaded (spec/10 §1.1, D-45, D-85).
Summary
Types
One editable node of a fragment (spec/14 §18.5).
The control a key's type gets (spec/14 §18.6).
Functions
What a change to the form means: the key it is about, and what to write there —
{:ok, value} or :error for a removal — or nil for a key the kind does not declare
(spec/14 §18.6, §19.9).
Every write a change to the form means (spec/14 §18.6): one for most controls, as
change/2 reads it, and two for a font_face — the weight word and the style word
of the face picked — since a face is one control over two keys.
The face a weight and a style show as (spec/14 §18.6): one of the eight words. A
missing key reads as :normal; an integer weight is the nearest of 300, 400, 500
and 700.
The eight faces in order, each with the weight and style it writes (spec/14 §18.6).
The facets a node kind has, in the order of spec/14 §1.3 (spec/14 §18.15).
One node's form alone (spec/14 §18.7): the fields of one facet — or, with only, of the
listed keys — as the controls widget/1 chose, carrying the node's label so an edit in
it aims at the node (_node, data-builder-node, §18.16).
A path as a person reads it — design for the root, else frame.scales.x,
marks[0].channels — Visualize.Chart.Validator.format_path/1, the one spelling an
error, a node label and a rendered data-node share (§4.7, §18.5).
The editable nodes of a fragment: one entry per node the schema's walk reaches.
Renders one form for one node (spec/10 §15.3).
A submitted string read back as a value of a type (spec/14 §18.6).
A submitted control's value read as a type, with the one rule parse/2 cannot know: an
unchecked checkbox submits nothing, which is false and not a removal, while every other
control's absence or blank is a removal (spec/14 §18.6).
A typed variable name as the variable it names, or :error for a blank — a removal.
The name becomes an atom exactly as a typed reference does (spec/14 §18.6, §19.9).
What a colour well shows for a value (spec/14 §19.10): the colour it resolves to — a
slot through the theme, a literal as itself — with the value's word as the title;
:none as no paint; anything else, or no theme for a slot, as nothing.
The control a type gets (spec/14 §18.6).
Types
@type node_entry() :: %{ path: Visualize.Chart.Validator.path(), kind: Visualize.Chart.Schema.kind(), label: String.t() }
One editable node of a fragment (spec/14 §18.5).
@type widget() :: :checkbox | :number | :text | :textarea | {:select, [atom()]} | {:pool, [String.t()]}
The control a key's type gets (spec/14 §18.6).
Functions
What a change to the form means: the key it is about, and what to write there —
{:ok, value} or :error for a removal — or nil for a key the kind does not declare
(spec/14 §18.6, §19.9).
key is the typed control and reads by parse/2; an unchecked checkbox submits
nothing, which is false and not a removal, and every other control's blank removes
the key. key.slot is a colour's slot face and key.range a number's slider, both
read as the key. A variable reaches a field as a chip dropped on it (§19.9), never as
a control of the form, so there is no key.mode or key.var.
iex> Visualize.Chart.Builder.Editor.change(:style, %{"_target" => ["stroke_width"], "stroke_width" => "5"})
{:stroke_width, {:ok, 5}}
iex> Visualize.Chart.Builder.Editor.change(:style, %{"_target" => ["fill.slot"], "fill.slot" => ""})
{:fill, :error}
iex> Visualize.Chart.Builder.Editor.change(:style, %{"_target" => ["nonsense"], "nonsense" => "1"})
nil
Every write a change to the form means (spec/14 §18.6): one for most controls, as
change/2 reads it, and two for a font_face — the weight word and the style word
of the face picked — since a face is one control over two keys.
iex> Visualize.Chart.Builder.Editor.changes(:style, %{"_target" => ["font_face"], "font_face" => "light_italic"})
[font_weight: {:ok, :light}, font_style: {:ok, :italic}]
iex> Visualize.Chart.Builder.Editor.changes(:style, %{"_target" => ["font_face"], "font_face" => "regular"})
[font_weight: {:ok, :normal}, font_style: {:ok, :normal}]
iex> Visualize.Chart.Builder.Editor.changes(:style, %{"_target" => ["stroke_width"], "stroke_width" => "5"})
[stroke_width: {:ok, 5}]
iex> Visualize.Chart.Builder.Editor.changes(:style, %{"_target" => ["nonsense"], "nonsense" => "1"})
[]
The face a weight and a style show as (spec/14 §18.6): one of the eight words. A
missing key reads as :normal; an integer weight is the nearest of 300, 400, 500
and 700.
iex> Visualize.Chart.Builder.Editor.face(:bold, :italic)
:bold_italic
iex> Visualize.Chart.Builder.Editor.face(nil, nil)
:regular
iex> Visualize.Chart.Builder.Editor.face(650, nil)
:bold
iex> Visualize.Chart.Builder.Editor.face(250, :italic)
:light_italic
The eight faces in order, each with the weight and style it writes (spec/14 §18.6).
iex> hd(Visualize.Chart.Builder.Editor.faces())
{:regular, :normal, :normal}
@spec facets(atom()) :: [Visualize.Chart.Schema.facet()]
The facets a node kind has, in the order of spec/14 §1.3 (spec/14 §18.15).
The editor renders these as its own strip; the builder renders them beside a node's children in one strip, and asks here rather than keeping a second copy of the rule.
iex> Visualize.Chart.Builder.Editor.facets(:style)
[:style]
iex> :binding in Visualize.Chart.Builder.Editor.facets(:mark)
true
One node's form alone (spec/14 §18.7): the fields of one facet — or, with only, of the
listed keys — as the controls widget/1 chose, carrying the node's label so an edit in
it aims at the node (_node, data-builder-node, §18.16).
Attributes
id(:string) - the editor's id, which the form carries as<id>-form; nil derives it from the node. Defaults tonil.kind(:atom) (required)node(:map) (required)path(:list) - Defaults to[].label(:string) - the node's label, aimed at by every event of the form. Defaults tonil.facet(:atom) - Defaults tonil.only(:list) - the keys to show, in schema order; nil for the facet's. Defaults tonil.errors(:list) - Defaults to[].refs(:map) - Defaults to%{}.selected(:string) - Defaults tonil.found(:string) - Defaults tonil.theme(:any) - Defaults tonil.defs(:map) - Defaults to%{}.pool(:list) - Defaults to[].hidden(:list) - Defaults to[].columns(:map) - the columns the pool's rows have, by source name (§18.6). Defaults to%{}.source_name(:any) - the name a keyed source site is declared under. Defaults tonil.channel_columns(:map) - Defaults to%{}.event(:string) - Defaults to"edit".origins(:map) - Defaults to%{}.locked(:boolean) - the node is the library's: the fields are disabled under a note (§18.15). Defaults tofalse.unlock(:any) - the row of the locked owner, which unlock on the note takes (§18.8, #381). Defaults tonil.guessed(:any) - the field labels —marks[0].channels.x— a placed mark guessed (§18.16). Defaults toMapSet.new([]).target(:any) - Defaults tonil.
@spec label([atom() | non_neg_integer()]) :: String.t()
A path as a person reads it — design for the root, else frame.scales.x,
marks[0].channels — Visualize.Chart.Validator.format_path/1, the one spelling an
error, a node label and a rendered data-node share (§4.7, §18.5).
iex> Visualize.Chart.Builder.Editor.label([:marks, 0, :style])
"marks[0].style"
@spec nodes(map()) :: [node_entry(), ...]
The editable nodes of a fragment: one entry per node the schema's walk reaches.
The root is first and carries the path []; a {:map, {:node, kind}} contributes one
node per declared name in name order, a {:list, {:node, kind}} one per element at its
index, and a {:one_of, …} that admits a node kind contributes that node when the
value is a map. The walk stops at values: a text, an extent, a channel and a reference
are edited on the node that carries them.
Examples
iex> fragment = %{version: 2, frames: %{main: %{kind: :cartesian}}}
iex> Enum.map(Visualize.Chart.Builder.Editor.nodes(fragment), & &1.label)
["design", "frames.main"]
iex> Enum.map(Visualize.Chart.Builder.Editor.nodes(fragment), & &1.kind)
[:design, :frame]
Renders one form for one node (spec/10 §15.3).
The facets of the node kind's keys are the tabs, in the order of spec/14 §1.3; the keys
of the open tab follow the order Visualize.Chart.Schema.describe/1 returns them in,
each with the control widget/1 chose, the value the node carries or the schema's
default as a placeholder, and its own errors under it.
Attributes
id(:string) - the editor's id, which its form carries as<id>-form; nil derives it from the node. Defaults tonil.kind(:atom) (required) - the node kind being edited.node(:map) (required) - the node's current value.path(:list) - where the node sits, for its keys' errors. Defaults to[].facet(:atom) - the open tab,nilfor the kind's first facet. Defaults tonil.errors(:list) - the{path, reason}pairs of spec/14 §10.1. Defaults to[].refs(:map) - the names and ids a{:ref, kind}may take, by kind — ids as{kind, n}. Defaults to%{}.selected(:string) - the key of the selected field, if any (§19.10). Defaults tonil.found(:string) - a variable name whose fields are highlighted (§19.10). Defaults tonil.theme(:any) - the%Visualize.Theme{}the design names, for what a colour well shows (spec/14 §19.10). Defaults tonil.defs(:map) - the design's paints, for a fill's style and its gradient's controls (spec/14 §18.6). Defaults to%{}.pool(:list) - the names of the host's sample sources, for a source'sdefault(spec/14 §19.10). Defaults to[].hidden(:list) - keys the host keeps out of a:sourceform —defaultwhen it binds by name alone (§19.10). Defaults to[].strip(:boolean) - render the facet strip;falsewhere the caller does. Defaults totrue.event(:string) - thephx-changethe form pushes. Defaults to"edit".origins(:map) - what supplied each key's value, by key. Defaults to%{}.label(:string) - the node's label, carried by the form (§18.16). Defaults tonil.only(:list) - the keys to show; nil for the open facet's. Defaults tonil.columns(:map) - the columns the pool's rows have, by source name (§18.6). Defaults to%{}.source_name(:any) - the name a keyed source site is declared under. Defaults tonil.target(:any) - thephx-targetof the form's events. Defaults tonil.class(:string) - appended to the editor's class. Defaults tonil.
@spec parse(Visualize.Chart.Schema.type(), String.t()) :: {:ok, term()} | :error
A submitted string read back as a value of a type (spec/14 §18.6).
A blank string is :error for every type: blanking a control removes the key rather
than setting it to nothing. A composite value is read as a literal — parsed with
Code.string_to_quoted/1 and never evaluated — and only a number, a string, an atom, a
boolean, nil, a list, a tuple, a map, a Visualize.Chart.Var or a ~U sigil is
accepted.
Examples
iex> Visualize.Chart.Builder.Editor.parse(:integer, "42")
{:ok, 42}
iex> Visualize.Chart.Builder.Editor.parse({:ref, :style}, "series")
{:ok, :series}
iex> Visualize.Chart.Builder.Editor.parse(:colour, "#3b6fa8")
{:ok, "#3b6fa8"}
iex> Visualize.Chart.Builder.Editor.parse(:extent, "[0, 100]")
{:ok, [0, 100]}
iex> Visualize.Chart.Builder.Editor.parse(:term, "System.halt()")
:error
@spec submitted(Visualize.Chart.Schema.type(), String.t() | nil) :: {:ok, term()} | :error
A submitted control's value read as a type, with the one rule parse/2 cannot know: an
unchecked checkbox submits nothing, which is false and not a removal, while every other
control's absence or blank is a removal (spec/14 §18.6).
iex> Visualize.Chart.Builder.Editor.submitted(:boolean, nil)
{:ok, false}
iex> Visualize.Chart.Builder.Editor.submitted(:integer, nil)
:error
@spec variable(String.t() | nil) :: {:ok, Visualize.Chart.Var.t()} | :error
A typed variable name as the variable it names, or :error for a blank — a removal.
The name becomes an atom exactly as a typed reference does (spec/14 §18.6, §19.9).
iex> Visualize.Chart.Builder.Editor.variable(":color_1")
{:ok, %Visualize.Chart.Var{name: :color_1}}
iex> Visualize.Chart.Builder.Editor.variable(" ")
:error
@spec well(term(), Visualize.Theme.t() | nil) :: %{ colour: String.t() | nil, none?: boolean(), word: String.t() | nil }
What a colour well shows for a value (spec/14 §19.10): the colour it resolves to — a
slot through the theme, a literal as itself — with the value's word as the title;
:none as no paint; anything else, or no theme for a slot, as nothing.
iex> Visualize.Chart.Builder.Editor.well("#3b6fa8", Visualize.Theme.default())
%{colour: "#3b6fa8", none?: false, word: "#3b6fa8"}
iex> Visualize.Chart.Builder.Editor.well(:series_1, Visualize.Theme.default())
%{colour: "#3b6fa8", none?: false, word: "series_1"}
iex> Visualize.Chart.Builder.Editor.well(:none, nil)
%{colour: nil, none?: true, word: "none"}
iex> Visualize.Chart.Builder.Editor.well(nil, nil)
%{colour: nil, none?: false, word: nil}
@spec widget(Visualize.Chart.Schema.type()) :: widget()
The control a type gets (spec/14 §18.6).
Examples
iex> Visualize.Chart.Builder.Editor.widget(:boolean)
:checkbox
iex> Visualize.Chart.Builder.Editor.widget({:enum, [:top, :bottom]})
{:select, [:top, :bottom]}
iex> Visualize.Chart.Builder.Editor.widget({:ref, :style})
:text
iex> Visualize.Chart.Builder.Editor.widget({:node, :margin})
:textarea