Visualize.IR.Element (Visualize v0.8.0)

Copy Markdown View Source

Backend-agnostic element representation for building scene graphs.

Elements represent visual primitives that can be rendered by any backend. The structure separates concerns:

  • type: The kind of element (:path, :rect, :circle, etc.)
  • attrs: Geometric attributes (x, y, width, height, cx, cy, r, etc.)
  • style: Visual styling (fill, stroke, opacity, etc.)
  • transform: Transformation to apply
  • children: Child elements (for groups)
  • content: Text content (for text elements)
  • path: Path IR (for path elements)

Examples

iex> alias Visualize.IR.{Element, Path}
iex> path = Path.new() |> Path.move_to(0, 0) |> Path.line_to(100, 100)
iex> element = Element.path(path, %{stroke: "blue", fill: :none})
iex> element.type
:path
iex> element.style
%{stroke: "blue", fill: :none}
iex> element.path
%Visualize.IR.Path{commands: [{:M, 0, 0}, {:L, 100, 100}]}

Summary

Types

Where an element is drawn (spec/02 §2.1): only where the shape is, or only where it is not — how a stroke is kept inside or outside an edge (spec/14 §5.6).

t()

Functions

Appends a child element.

Creates a circle element.

The element drawn only where shape is (:inside) or only where it is not (:outside) (spec/02 §2.1). The shape's style is ignored by every backend.

Creates a clip path element.

Writes a datum's fields onto the element as the data attributes TooltipHook reads (spec/10 §12.2, D-75).

Creates a defs element (for gradients, patterns, etc.).

Creates a desc element: the accessible description of its parent.

A drop shadow primitive of the offset and the blur radius, at 0.35 opacity.

A drop shadow primitive of the offset, the blur radius and a color: flood-color the color at flood-opacity 1, so the color's own alpha is the shadow's (#674); a nil color is drop_shadow/3.

Creates an ellipse element.

A filter (spec/02 §2.1): its id and its primitives, for an element's effect.

A Gaussian blur primitive of the radius.

Creates a group element.

Creates a line element.

Creates a linear gradient element.

Creates a new element of the specified type.

Creates a path element from a Path IR.

Creates a polygon element. Points should be a list of {x, y} tuples.

Creates a polyline element. Points should be a list of {x, y} tuples.

Prepends a child element.

The element with one attribute set: key — an atom, or a string for an attribute the IR has no name for, such as a data- attribute — to value.

Creates a radial gradient element.

Creates a rectangle element.

Creates a root SVG element, role="img".

Sets attributes on an element, merging with existing attrs.

Sets the text content of an element.

Sets style properties on an element, merging with existing style.

Sets the transform on an element.

Creates a gradient stop element.

A :style with the given stylesheet as content: a root's automatic dark mode (spec/08 §7.3, #668). Only the SVG backend writes it; a canvas draws nothing for it.

Creates a text element with dx/dy offsets (useful for tick labels).

Creates a text element.

Creates a title element: the accessible name of the element it is the first child of.

Adds a translation transform.

Wraps element(s) in a group with a translation transform.

One line of a :text (spec/02 §2.1): a :tspan with its content and attrs, x and dy among them. A :text whose children are tspans and whose content is nil is a text of several lines (spec/14 §7.1).

Types

clip()

@type clip() :: %{shape: t(), keep: :inside | :outside}

Where an element is drawn (spec/02 §2.1): only where the shape is, or only where it is not — how a stroke is kept inside or outside an edge (spec/14 §5.6).

element_type()

@type element_type() ::
  :path
  | :rect
  | :circle
  | :ellipse
  | :line
  | :polyline
  | :polygon
  | :text
  | :group
  | :image
  | :defs
  | :clip_path
  | :linear_gradient
  | :radial_gradient
  | :pattern
  | :stop
  | :filter
  | :fe_drop_shadow
  | :fe_gaussian_blur
  | :tspan
  | :root
  | :title
  | :desc
  | :style

style()

@type style() :: %{
  optional(:fill) =>
    String.t() | :none | :current_color | Visualize.IR.Paint.t(),
  optional(:stroke) =>
    String.t() | :none | :current_color | Visualize.IR.Paint.t(),
  optional(:stroke_width) => number(),
  optional(:stroke_linecap) => :butt | :round | :square,
  optional(:stroke_linejoin) => :miter | :round | :bevel,
  optional(:stroke_dasharray) => String.t(),
  optional(:opacity) => number(),
  optional(:fill_opacity) => number(),
  optional(:stroke_opacity) => number(),
  optional(:font_size) => number() | String.t(),
  optional(:font_family) => String.t(),
  optional(:font_weight) => String.t() | number() | :normal | :bold,
  optional(:font_style) => :normal | :italic,
  optional(:text_anchor) => :start | :middle | :end,
  optional(:dominant_baseline) => atom(),
  optional(:class) => String.t(),
  optional(:line_height) => number(),
  optional(:blend) => atom(),
  optional(:effect) => :none | :shadow | :blur,
  optional(:effect_radius) => number(),
  optional(:vector_effect) => :non_scaling_stroke
}

t()

@type t() :: %Visualize.IR.Element{
  attrs: map(),
  children: [t()],
  clip: clip() | nil,
  content: String.t() | nil,
  path: Visualize.IR.Path.t() | nil,
  style: style(),
  transform: Visualize.IR.Transform.t() | nil,
  type: element_type()
}

Functions

append(parent, child)

@spec append(t(), t() | [t()]) :: t()

Appends a child element.

circle(cx, cy, r, style \\ %{})

@spec circle(number(), number(), number(), style()) :: t()

Creates a circle element.

clip(element, shape, keep)

@spec clip(t(), t(), :inside | :outside) :: t()

The element drawn only where shape is (:inside) or only where it is not (:outside) (spec/02 §2.1). The shape's style is ignored by every backend.

iex> square = Visualize.IR.Element.rect(0, 0, 10, 10)
iex> Visualize.IR.Element.clip(square, square, :inside).clip.keep
:inside

clip_path(id, children)

@spec clip_path(String.t(), [t()]) :: t()

Creates a clip path element.

datum(element, fields, names \\ %{})

@spec datum(t(), map() | keyword(), map()) :: t()

Writes a datum's fields onto the element as the data attributes TooltipHook reads (spec/10 §12.2, D-75).

fields is a map — written in Enum.sort/1 order — or a keyword list, in its order. data-datum lists the field names; data-datum-<field> carries to_string/1 of each value, with every _ in the name as -, and a nil value writes no attribute. The keys are strings, so no atom is created from a row's names.

names, a map from a field to its value's display name, writes data-named-<field> beside data-datum-<field> for each field written that it holds (spec/10 §12.2, D-225): the tooltip shows the name, and the value stays the value. datum/2 writes none.

Examples

iex> Visualize.IR.Element.circle(1, 2, 3)
...> |> Visualize.IR.Element.datum(at: ~U[2024-01-01 00:00:00Z], big_v: 4, gap: nil)
...> |> Map.fetch!(:attrs)
%{
  "data-datum" => "at big_v gap",
  "data-datum-at" => "2024-01-01 00:00:00Z",
  "data-datum-big-v" => "4",
  cx: 1,
  cy: 2,
  r: 3
}

iex> Visualize.IR.Element.circle(1, 2, 3)
...> |> Visualize.IR.Element.datum([state: "degraded", n: 2], %{state: "Degraded"})
...> |> Map.fetch!(:attrs)
...> |> Map.take(["data-datum-state", "data-named-state", "data-named-n"])
%{"data-datum-state" => "degraded", "data-named-state" => "Degraded"}

defs(children \\ [])

@spec defs([t()]) :: t()

Creates a defs element (for gradients, patterns, etc.).

desc(text)

@spec desc(String.t()) :: t()

Creates a desc element: the accessible description of its parent.

drop_shadow(dx, dy, radius)

@spec drop_shadow(number(), number(), number()) :: t()

A drop shadow primitive of the offset and the blur radius, at 0.35 opacity.

drop_shadow(dx, dy, radius, color)

@spec drop_shadow(number(), number(), number(), String.t() | nil) :: t()

A drop shadow primitive of the offset, the blur radius and a color: flood-color the color at flood-opacity 1, so the color's own alpha is the shadow's (#674); a nil color is drop_shadow/3.

ellipse(cx, cy, rx, ry, style \\ %{})

@spec ellipse(number(), number(), number(), number(), style()) :: t()

Creates an ellipse element.

filter(id, children)

@spec filter(String.t(), [t()]) :: t()

A filter (spec/02 §2.1): its id and its primitives, for an element's effect.

gaussian_blur(radius)

@spec gaussian_blur(number()) :: t()

A Gaussian blur primitive of the radius.

group(children \\ [], opts \\ [])

@spec group([t()], keyword()) :: t()

Creates a group element.

image(href, x, y, width, height, style \\ %{})

@spec image(String.t(), number(), number(), number(), number(), style()) :: t()

Creates an image element.

line(x1, y1, x2, y2, style \\ %{})

@spec line(number(), number(), number(), number(), style()) :: t()

Creates a line element.

linear_gradient(id, opts \\ [])

@spec linear_gradient(String.t(), keyword()) :: t()

Creates a linear gradient element.

new(type, opts \\ [])

@spec new(element_type(), keyword()) :: t()

Creates a new element of the specified type.

path(path_ir, style \\ %{})

@spec path(Visualize.IR.Path.t(), style()) :: t()

Creates a path element from a Path IR.

polygon(points, style \\ %{})

@spec polygon([{number(), number()}], style()) :: t()

Creates a polygon element. Points should be a list of {x, y} tuples.

polyline(points, style \\ %{})

@spec polyline([{number(), number()}], style()) :: t()

Creates a polyline element. Points should be a list of {x, y} tuples.

prepend(parent, child)

@spec prepend(t(), t() | [t()]) :: t()

Prepends a child element.

put_attr(element, key, value)

@spec put_attr(t(), atom() | String.t(), term()) :: t()

The element with one attribute set: key — an atom, or a string for an attribute the IR has no name for, such as a data- attribute — to value.

iex> Visualize.IR.Element.group([]) |> Visualize.IR.Element.put_attr("data-node", "marks[0]") |> Map.fetch!(:attrs)
%{"data-node" => "marks[0]"}

radial_gradient(id, opts \\ [])

@spec radial_gradient(String.t(), keyword()) :: t()

Creates a radial gradient element.

rect(x, y, width, height, style \\ %{})

@spec rect(number(), number(), number(), number(), style()) :: t()

Creates a rectangle element.

root(width, height, opts \\ [])

@spec root(number(), number(), keyword()) :: t()

Creates a root SVG element, role="img".

Options

  • :view_box - the viewBox (default "0 0 <width> <height>")
  • :children - the child elements (default [])
  • :preserve_aspect_ratio - the preserveAspectRatio attribute; absent by default
  • :responsive - when true, preserveAspectRatio="xMidYMid meet" (unless given) and style="width:100%;height:auto", so the <svg> fills its container's width at the drawing's aspect ratio while width and height stay its intrinsic size (D-54)
  • :title, :description - the accessible name and description (D-56, D-142, D-201): the title is the root's aria-label, never a <title> child, which a browser shows as a tooltip under the pointer; the description is a <desc> with an id, the first child, which aria-describedby names, so a screen reader says a short name and then the description — a description alone is named by aria-labelledby, as it is the only text the root has
  • :hidden - aria-hidden="true" on the root: one layer of a picture another element names, as a hybrid chart's SVG layers are (spec/14 §21.4)
  • :id - the root's id and the prefix of the description's id (<id>-desc); defaults to vis- and a hash of the two texts, and is only set when a title or description is given

set_attrs(element, new_attrs)

@spec set_attrs(t(), map()) :: t()

Sets attributes on an element, merging with existing attrs.

set_content(element, content)

@spec set_content(t(), String.t()) :: t()

Sets the text content of an element.

set_style(element, new_style)

@spec set_style(t(), style()) :: t()

Sets style properties on an element, merging with existing style.

set_transform(element, transform)

@spec set_transform(t(), Visualize.IR.Transform.t()) :: t()

Sets the transform on an element.

stop(offset, color, opts \\ [])

@spec stop(String.t(), String.t(), keyword()) :: t()

Creates a gradient stop element.

stylesheet(css)

@spec stylesheet(String.t()) :: t()

A :style with the given stylesheet as content: a root's automatic dark mode (spec/08 §7.3, #668). Only the SVG backend writes it; a canvas draws nothing for it.

iex> Visualize.IR.Element.stylesheet("svg { color: red }").type
:style

text(content, opts)

@spec text(String.t(), keyword()) :: t()

Creates a text element with dx/dy offsets (useful for tick labels).

text(content, x, y, style \\ %{})

@spec text(String.t(), number(), number(), style()) :: t()

Creates a text element.

title(text)

@spec title(String.t()) :: t()

Creates a title element: the accessible name of the element it is the first child of.

translate(element, x, y)

@spec translate(t(), number(), number()) :: t()

Adds a translation transform.

translate_group(elements, x, y)

@spec translate_group(t() | [t()], number(), number()) :: t()

Wraps element(s) in a group with a translation transform.

tspan(content, attrs)

@spec tspan(String.t(), map()) :: t()

One line of a :text (spec/02 §2.1): a :tspan with its content and attrs, x and dy among them. A :text whose children are tspans and whose content is nil is a text of several lines (spec/14 §7.1).