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

Copy Markdown View Source

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

node_entry()

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

widget()

@type widget() ::
  :checkbox
  | :number
  | :text
  | :textarea
  | {:select, [atom()]}
  | {:pool, [String.t()]}

The control a key's type gets (spec/14 §18.6).

Functions

change(kind, params)

@spec change(atom(), map()) :: {atom(), {:ok, term()} | :error} | nil

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

changes(kind, params)

@spec changes(atom(), map()) :: [{atom(), {:ok, term()} | :error}]

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"})
[]

face(weight, style)

@spec face(term(), term()) :: atom()

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

faces()

@spec faces() :: [{atom(), atom(), atom()}]

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}

facets(kind)

@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

form_body(assigns)

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 to nil.
  • 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 to nil.
  • facet (:atom) - Defaults to nil.
  • only (:list) - the keys to show, in schema order; nil for the facet's. Defaults to nil.
  • errors (:list) - Defaults to [].
  • refs (:map) - Defaults to %{}.
  • selected (:string) - Defaults to nil.
  • found (:string) - Defaults to nil.
  • theme (:any) - Defaults to nil.
  • 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 to nil.
  • 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 to false.
  • unlock (:any) - the row of the locked owner, which unlock on the note takes (§18.8, #381). Defaults to nil.
  • guessed (:any) - the field labels — marks[0].channels.x — a placed mark guessed (§18.16). Defaults to MapSet.new([]).
  • target (:any) - Defaults to nil.

label(path)

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

nodes(fragment)

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

panel(assigns)

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 to nil.
  • 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, nil for the kind's first facet. Defaults to nil.
  • 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 to nil.
  • found (:string) - a variable name whose fields are highlighted (§19.10). Defaults to nil.
  • theme (:any) - the %Visualize.Theme{} the design names, for what a colour well shows (spec/14 §19.10). Defaults to nil.
  • 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's default (spec/14 §19.10). Defaults to [].
  • hidden (:list) - keys the host keeps out of a :source form — default when it binds by name alone (§19.10). Defaults to [].
  • strip (:boolean) - render the facet strip; false where the caller does. Defaults to true.
  • event (:string) - the phx-change the 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 to nil.
  • only (:list) - the keys to show; nil for the open facet's. Defaults to nil.
  • 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 to nil.
  • target (:any) - the phx-target of the form's events. Defaults to nil.
  • class (:string) - appended to the editor's class. Defaults to nil.

parse(type, string)

@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

submitted(type, string)

@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

variable(string)

@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

well(hex, theme)

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

widget(type)

@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