Status: Implemented

Visualize is a pipeline of pure values: data is mapped by scales, turned into geometry by shapes, assembled into a backend-agnostic scene graph (the IR), and finally serialised by a backend. This document settles the pipeline, the three IR modules, the backend behaviour and its resolution, the Visualize.Render entry point, and the SVG element layer that faces HEEx templates.

1. The pipeline

data ──► Visualize.Scale ──► Visualize.Shape ──► Visualize.IR.Element ──► Visualize.Backend ──► Visualize.Render
          domain → range     path strings or      scene graph of          SVG | Canvas          iolist | String
                             IR.Path               elements + transforms   (+ encoders)
  1. Scales (Visualize.Scale, 03-scales) map domain values to range values. They are structs; apply/2 performs the mapping.
  2. Shapes (Visualize.Shape, 04-shapes-and-curves) turn scaled data into geometry. generate/2 returns an SVG path-data string; generate_path/2 returns a Visualize.IR.Path.
  3. The IR (Visualize.IR.*, §2) is the scene graph: Element nodes carrying attributes, style, an optional Transform, children, text content or a Path. Visualize.Axis.generate/1 and the layouts emit IR.
  4. Backends (Visualize.Backend, §3) serialise the IR. Visualize.Backend.SVG and Visualize.Backend.Canvas implement the behaviour. Visualize.Backend.CanvasBinary, Visualize.Backend.Hybrid and Visualize.Backend.CanvasIncremental are encoders with their own entry points, not behaviour implementations; they are specified in 09-rendering-backends.
  5. Render (Visualize.Render, §4) chooses a backend and dispatches on the IR's shape.

Every stage is a pure function of its inputs. No stage holds process state; the only process in the library is the force simulation (06-layouts).

2. The intermediate representation

2.1 Visualize.IR.Element

An element is %Visualize.IR.Element{type, attrs, style, transform, children, content, path, clip}. clip is nil or %{shape: element, keep: :inside | :outside}: the element is drawn only where the shape is (:inside) or only where it is not (:outside), the shape being a :path, :rect, :circle, :ellipse or :polygon element whose style is ignored — how a stroke is kept inside or outside an edge (spec/14 §5.6). A backend that can clip MUST honour it (spec/09 §2.3, §3.3); one that cannot draws the element unclipped and says so. type MUST be one of :path, :rect, :circle, :ellipse, :line, :polyline, :polygon, :text, :group, :image, :defs, :clip_path, :linear_gradient, :radial_gradient, :stop, :filter, :fe_drop_shadow, :fe_gaussian_blur, :tspan, :root, :title, :desc. attrs is a map of geometric attributes; style is a map whose recognised keys are :fill, :stroke, :stroke_width, :stroke_linecap (:butt | :round | :square), :stroke_linejoin (:miter | :round | :bevel), :stroke_dasharray, :opacity, :fill_opacity, :stroke_opacity, :font_size, :font_family, :font_weight, :font_style, :text_anchor (:start | :middle | :end), :dominant_baseline, :class, :blend (a mix-blend-mode word), :effect (:none | :shadow | :blur) with :effect_radius; :fill and :stroke accept a string, :none or :current_color. Gradient stops additionally use :stop_color and :stop_opacity. Backends MUST pass unrecognised style keys through unchanged. Constructors with a default style argument default it to %{}.

FunctionContract
Visualize.IR.Element.new/1As new/2 with no options.
Visualize.IR.Element.new/2Builds an element of the given type from options :attrs (default %{}), :style (%{}), :transform (nil), :children ([]), :content (nil), :path (nil).
Visualize.IR.Element.path/1As path/2 with an empty style.
Visualize.IR.Element.path/2Wraps a Visualize.IR.Path in a :path element with the given style.
Visualize.IR.Element.group/0An empty :group.
Visualize.IR.Element.group/1A :group with the given children.
Visualize.IR.Element.group/2A :group with children and options :style, :transform, :attrs.
Visualize.IR.Element.rect/4As rect/5 with an empty style.
Visualize.IR.Element.rect/5A :rect with attrs x, y, width, height.
Visualize.IR.Element.circle/3As circle/4 with an empty style.
Visualize.IR.Element.circle/4A :circle with attrs cx, cy, r.
Visualize.IR.Element.ellipse/4As ellipse/5 with an empty style.
Visualize.IR.Element.ellipse/5An :ellipse with attrs cx, cy, rx, ry.
Visualize.IR.Element.line/4As line/5 with an empty style.
Visualize.IR.Element.line/5A :line with attrs x1, y1, x2, y2.
Visualize.IR.Element.polyline/1As polyline/2 with an empty style.
Visualize.IR.Element.polyline/2A :polyline whose points attr is a list of {x, y} tuples.
Visualize.IR.Element.polygon/1As polygon/2 with an empty style.
Visualize.IR.Element.polygon/2A :polygon whose points attr is a list of {x, y} tuples.
Visualize.IR.Element.text/2A :text element from content and a keyword list; takes :x, :y, :dx, :dy into attrs and :style into style.
Visualize.IR.Element.text/3As text/4 with an empty style.
Visualize.IR.Element.text/4A :text element with content and attrs x, y.
Visualize.IR.Element.tspan/2tspan(content, attrs): a :tspan — one line of a :text, a child of it — with the given attrs (x, dy). A :text with :tspan children and no content is a text of several lines (spec/14 §7.1).
Visualize.IR.Element.image/5As image/6 with an empty style.
Visualize.IR.Element.image/6An :image with attrs href, x, y, width, height.
Visualize.IR.Element.defs/0An empty :defs.
Visualize.IR.Element.defs/1A :defs with the given children.
Visualize.IR.Element.clip_path/2A :clip_path with attr id and the given children.
Visualize.IR.Element.linear_gradient/1As linear_gradient/2 with no options.
Visualize.IR.Element.linear_gradient/2A :linear_gradient with attr id, optional :x1, :y1, :x2, :y2 attrs, and children from :stops (default []).
Visualize.IR.Element.radial_gradient/1As radial_gradient/2 with no options.
Visualize.IR.Element.radial_gradient/2A :radial_gradient with attr id, optional :cx, :cy, :r, :fx, :fy attrs, and children from :stops.
Visualize.IR.Element.stop/2As stop/3 with no options.
Visualize.IR.Element.stop/3A :stop with attr offset, style stop_color, and stop_opacity when :opacity is given.
Visualize.IR.Element.filter/2filter(id, children): a :filter with attr id and the given primitives.
Visualize.IR.Element.drop_shadow/3drop_shadow(dx, dy, radius): a :fe_drop_shadow with attrs dx, dy, std_deviation and flood_opacity 0.35.
Visualize.IR.Element.gaussian_blur/1gaussian_blur(radius): a :fe_gaussian_blur with attr std_deviation.
Visualize.IR.Element.title/1A :title with the given text as content: the accessible name of the element it is the first child of.
Visualize.IR.Element.desc/1A :desc with the given text as content: the accessible description.
Visualize.IR.Element.root/2As root/3 with no options.
Visualize.IR.Element.root/3A :root with attrs width, height, view_box (default "0 0 #{width} #{height}") and children from :children, plus the responsive options of D-54: :preserve_aspect_ratio sets the attr of that name (rendered preserveAspectRatio, spec/09 §2.4), absent by default; responsive: true sets it to "xMidYMid meet" unless given and adds style: "width:100%;height:auto", so the <svg> keeps width and height as its intrinsic size and aspect ratio but fills the width of its container. Every root carries role: "img" (D-56). The accessibility options: :title and :description prepend a title/1 and a desc/1 child, each with an id, and set aria_labelledby (rendered aria-labelledby) to those ids in that order; :id is the prefix of the child ids (<id>-title, <id>-desc) and is also set on the root, and defaults to vis- followed by the lower-case base-36 :erlang.phash2/1 of {title, description}, so equal charts share ids and different ones do not; with neither text no child, no aria_labelledby and no id are added.
Visualize.IR.Element.put_attr/3The element with one attribute set: an atom key, or a string for an attribute the IR has no name for, such as data-node (spec/14 §4.7).
Visualize.IR.Element.append/2Appends one element or a list of elements to children.
Visualize.IR.Element.prepend/2Prepends one element or a list of elements to children.
Visualize.IR.Element.set_transform/2Replaces the element's transform.
Visualize.IR.Element.translate/3Adds a translate operation: creates the transform when nil, otherwise appends to it.
Visualize.IR.Element.set_style/2Merges the given style over the existing style.
Visualize.IR.Element.clip/3clip(element, shape, keep): the element with clip set to the shape and :inside or :outside.
Visualize.IR.Element.set_attrs/2Merges the given attrs over the existing attrs.
Visualize.IR.Element.datum/2Writes the datum attributes of spec/10 §12.2 over the element's attrs, under string keys: "data-datum", the field names space-separated in order, and "data-datum-<field>" per field with _ as - and to_string/1 of the value, a nil value omitted; the fields are a map (written in Enum.sort/1 order) or a keyword list (in its order). What TooltipHook reads (D-75).
Visualize.IR.Element.set_content/2Sets the text content.
Visualize.IR.Element.translate_group/3Wraps an element or list of elements in a :group carrying Transform.translate(x, y).

Visualize.IR.Element implements String.Chars (renders through Visualize.Backend.SVG) and, when Phoenix.HTML.Safe is loaded, Phoenix.HTML.Safe (D-7).

2.2 Visualize.IR.Path

A path is %Visualize.IR.Path{commands, metadata}; metadata is a free map for producers and is not serialised. Commands are tuples tagged with the SVG command letter — absolute upper-case, relative lower-case: {:M|:m, x, y}, {:L|:l, x, y}, {:H|:h, x}, {:V|:v, y}, {:C|:c, x1, y1, x2, y2, x, y}, {:S|:s, x2, y2, x, y}, {:Q|:q, x1, y1, x, y}, {:T|:t, x, y}, {:A|:a, rx, ry, rotation, large_arc, sweep, x, y} and the bare atom :Z. large_arc and sweep MUST be 0 or 1. Builders append in call order. A path is built in time proportional to its commands (#414): the appenders above put one command at the end of a list, which is the cost of the list, so a builder that draws one command per datum makes its commands in order and hands them to from_commands/1 at the end — a path of 50,000 commands took six seconds to append one at a time and the drawing of a large chart was quadratic in its points.

FunctionContract
Visualize.IR.Path.new/0An empty path.
Visualize.IR.Path.from_commands/1A path of the commands given, in the order given: what a builder that draws per datum ends with, so building costs the list and not a square of it (#414).
Visualize.IR.Path.move_to/3Appends {:M, x, y}.
Visualize.IR.Path.move_to_rel/3Appends {:m, dx, dy}.
Visualize.IR.Path.line_to/3Appends {:L, x, y}.
Visualize.IR.Path.line_to_rel/3Appends {:l, dx, dy}.
Visualize.IR.Path.horizontal_to/2Appends {:H, x}.
Visualize.IR.Path.horizontal_to_rel/2Appends {:h, dx}.
Visualize.IR.Path.vertical_to/2Appends {:V, y}.
Visualize.IR.Path.vertical_to_rel/2Appends {:v, dy}.
Visualize.IR.Path.curve_to/7Appends a cubic Bézier {:C, x1, y1, x2, y2, x, y}.
Visualize.IR.Path.curve_to_rel/7Appends {:c, ...} with relative coordinates.
Visualize.IR.Path.smooth_curve_to/5Appends {:S, x2, y2, x, y}.
Visualize.IR.Path.smooth_curve_to_rel/5Appends {:s, ...}.
Visualize.IR.Path.quad_to/5Appends a quadratic Bézier {:Q, x1, y1, x, y}.
Visualize.IR.Path.quad_to_rel/5Appends {:q, ...}.
Visualize.IR.Path.smooth_quad_to/3Appends {:T, x, y}.
Visualize.IR.Path.smooth_quad_to_rel/3Appends {:t, dx, dy}.
Visualize.IR.Path.arc_to/8Appends {:A, rx, ry, x_rotation, large_arc, sweep, x, y}; rotation in degrees.
Visualize.IR.Path.arc_to_rel/8Appends {:a, ...} with a relative end point.
Visualize.IR.Path.close/1Appends :Z.
Visualize.IR.Path.empty?/1true iff there are no commands.
Visualize.IR.Path.command_count/1The number of commands.
Visualize.IR.Path.concat/1One path whose commands are the concatenation of the given paths' commands, in order; metadata is dropped.
Visualize.IR.Path.append/2The first path's commands followed by the second's; metadata is dropped.
Visualize.IR.Path.expand_smooth/1Rewrites S/s/T/t as C/c/Q/q (D-36): the first control point is the reflection about the current point of the previous command's last control point when that command was a cubic (for S/s) or a quadratic (for T/t), and the current point otherwise; a relative result keeps its deltas relative. Every other command is unchanged.
Visualize.IR.Path.absolute/1Every command as an absolute M, L, C, Q, A or Z: relative commands resolved against the current point (a leading m is absolute), H/V as L, smooth curves expanded as expand_smooth/1 does, arcs keeping radii, rotation and flags. Draws the same figure; this is what a target without relative commands replays.
Visualize.IR.Path.transform/2Applies an affine matrix {a, b, c, d, e, f} (Visualize.IR.Transform.matrix/0), or a Visualize.IR.Transform folded with Visualize.IR.Transform.to_matrix/1, to the path (D-35). Absolute end and control points get the full map {a·x + c·y + e, b·x + d·y + f}; relative deltas get the linear part only. The current point is tracked through every command (:Z returns to the subpath start; a leading m is absolute, as SVG reads it): H/h stay single-axis when b = 0 and V/v when c = 0, otherwise they become L/l to the mapped point. An arc's end point is mapped; its radii and rotation are those of the exact image of its ellipse under the linear part (the singular values and left rotation of the 2×2 product), in canonical form rx ≥ ry, rotation in [0, 180), a circle keeping its given rotation; the sweep flag flips when the determinant is negative; large_arc is untouched.
Visualize.IR.Path.to_string/1The SVG d string: commands concatenated with no separators, coordinates comma-separated, e.g. "M10,20L100,200Z"; an arc prints Arx,ry,rot,large,sweep,x,y with the two flags verbatim. Every number goes through format_number/1.
Visualize.IR.Path.format_number/1The one number formatter of every serialiser (D-12, D-32): an integer prints as-is; a float prints with at most four decimals, trailing zeros and a trailing point removed, never in exponent notation, and never as -0 (a zero that rounds negative prints 0).

to_string/1 is the only d serialisation: Visualize.Backend.SVG.path_data/1, Visualize.SVG.Path.to_string/1 and the String.Chars implementation for Visualize.IR.Path (defined alongside Visualize.SVG.Path) delegate to it (D-12, D-34).

2.3 Visualize.IR.Transform

A transform is %Visualize.IR.Transform{operations}, an ordered list applied left to right as SVG does: {:translate, x, y}, {:rotate, angle}, {:rotate, angle, cx, cy}, {:scale, sx, sy}, {:skew_x, angle}, {:skew_y, angle}, {:matrix, a, b, c, d, e, f}. Angles are degrees.

FunctionContract
Visualize.IR.Transform.new/0An empty transform.
Visualize.IR.Transform.translate/2A new transform with a single translate.
Visualize.IR.Transform.translate/3Appends a translate to the given transform.
Visualize.IR.Transform.rotate/1A new transform with a single rotate.
Visualize.IR.Transform.rotate/2Appends a rotate about the origin.
Visualize.IR.Transform.rotate/4Appends a rotate about {cx, cy}.
Visualize.IR.Transform.scale/1A new transform with a single uniform scale {:scale, s, s}.
Visualize.IR.Transform.scale/2Two numbers: a new transform with {:scale, sx, sy}. A transform and one number: appends a uniform scale to it (D-1).
Visualize.IR.Transform.scale/3Appends {:scale, sx, sy}.
Visualize.IR.Transform.skew_x/2Appends {:skew_x, angle}.
Visualize.IR.Transform.skew_y/2Appends {:skew_y, angle}.
Visualize.IR.Transform.matrix/7Appends {:matrix, a, b, c, d, e, f} for the matrix [a c e; b d f; 0 0 1].
Visualize.IR.Transform.empty?/1true iff there are no operations.
Visualize.IR.Transform.concat/2The first transform's operations followed by the second's.
Visualize.IR.Transform.to_matrix/1The operations folded into one affine matrix {a, b, c, d, e, f} for [a c e; b d f; 0 0 1], composed as SVG composes the attribute string: M = Op₁ · Op₂ · … · Opₙ, so the last operation applies to a point first. translate is [1 0 tx; 0 1 ty], scale [sx 0 0; 0 sy 0], rotate(a) [cos −sin 0; sin cos 0], rotate(a, cx, cy) is translate(cx, cy) rotate(a) translate(−cx, −cy), skew_x(a) [1 tan 0; 0 1 0], skew_y(a) [1 0 0; tan 1 0], matrix as given; angles in degrees. An empty transform is the identity.
Visualize.IR.Transform.multiply/2The product m1 · m2 of two matrices: applying the result is applying m2 first, then m1.
Visualize.IR.Transform.apply_matrix/2apply_matrix({a, b, c, d, e, f}, {x, y}) is {a·x + c·y + e, b·x + d·y + f}.
Visualize.IR.Transform.to_string/1The SVG transform attribute: operations space-separated, e.g. "translate(100,50) rotate(45) scale(2)". A scale with equal factors prints the single-argument form. Numbers format with Visualize.IR.Path.format_number/1, so -0 never prints. This is the only transform serialisation; Visualize.Backend.SVG emits it (D-34).

3. The backend behaviour

3.1 Callbacks

Visualize.Backend is a behaviour. An implementation MUST define:

  • path_data(Visualize.IR.Path.t()) :: String.t() | [tuple()] — the backend's native path data (the d string for SVG; a list of drawing commands for Canvas).

  • render_path(Visualize.IR.Path.t(), keyword()) :: output — a rendered path; options MAY include :style.
  • render_element(Visualize.IR.Element.t(), keyword()) :: output — a rendered element tree.
  • render_scene([Visualize.IR.Element.t()], keyword()) :: output — a rendered list of elements; the built-in backends wrap the result with wrap_root/2 unless wrap: false.

and MAY define wrap_root(output, keyword()) :: output, which wraps content in a root container (<svg> for SVG; canvas setup for Canvas) using options :width (default 800), :height (default 600) and :view_box. output is an iolist, string, map or command list as the backend chooses. The callback implementations on Visualize.Backend.SVG and Visualize.Backend.Canvas are specified in 09-rendering-backends.

3.2 Resolution and configuration

FunctionContract
Visualize.Backend.default/0The module from config :visualize, default_backend: resolved through resolve/1; Visualize.Backend.SVG when unset.
Visualize.Backend.resolve/1nil → default/0; :svg → Visualize.Backend.SVG; :canvas → Visualize.Backend.Canvas; any other atom is returned as a module unchanged.

config :visualize, default_backend: value accepts the same values as resolve/1. There is deliberately no per-process override (D-2): the backend is either the :backend option at the call site or the application configuration.

4. Visualize.Render

Visualize.Render dispatches a renderable — a Visualize.IR.Path, a Visualize.IR.Element, or a list of elements — to render_path/2, render_element/2 or render_scene/2 respectively. Every function takes an options keyword list; :backend (:svg, :canvas, or a module) selects the backend and all other options pass through to it.

FunctionContract
Visualize.Render.render/1As render/2 with the default backend.
Visualize.Render.render/2Renders to the backend's native output (an iolist for the built-in backends).
Visualize.Render.to_string/1As to_string/2 with the default backend.
Visualize.Render.to_string/2render/2 converted with IO.iodata_to_binary/1.
Visualize.Render.to_svg/1As to_svg/2.
Visualize.Render.to_svg/2render/2 with backend: :svg forced.
Visualize.Render.to_svg_string/1As to_svg_string/2.
Visualize.Render.to_svg_string/2to_string/2 with backend: :svg forced.
Visualize.Render.to_canvas/1As to_canvas/2.
Visualize.Render.to_canvas/2render/2 with backend: :canvas forced.
Visualize.Render.to_canvas_string/1As to_canvas_string/2.
Visualize.Render.to_canvas_string/2to_string/2 with backend: :canvas forced.
Visualize.Render.path_data/1As path_data/2 with the default backend.
Visualize.Render.path_data/2The selected backend's path_data/1 for the path.
Visualize.Render.to_png/1As to_png/2 with no options.
Visualize.Render.to_png/2A root Visualize.IR.Element, or an SVG document as a binary, rasterised to a PNG by the resvg command-line tool, run as a port: {:ok, png, warnings}, the warnings resvg printed ({:missing_family, list} for each font-family list no font resolves, {:rasterizer, line} for any other line, [] when there are none), {:error, :no_rasterizer} with no resvg binary configured or on the PATH, {:error, {:rasterizer_version, found, required}} for a binary older than the supported minimum, {:error, :css_references} when the SVG holds a var(-- reference, {:error, :timeout} when the render outlasts :timeout, {:error, {:rasterizer, message}} when resvg refuses the document. Options :scale, :background, :timeout, and the font options :font_dirs, :system_fonts, :generic_families and :font_family; 09-rendering-backends §9.
Visualize.Render.to_png!/1As to_png!/2 with no options.
Visualize.Render.to_png!/2As to_png/2, returning the PNG binary of {:ok, png, []} and raising RuntimeError on the {:error, _} branch and when there is a warning: a complete picture or none.

to_png/2 is the one function of the module that produces no backend output: it renders an IR root with to_svg_string/2 and hands the SVG to the rasteriser of 09-rendering-backends §9 (D-121). Its input is narrower than a renderable, because a PNG is a whole document: a :root element (Visualize.IR.Element.root/3, or Visualize.Chart.generate/2 with root: true), or the SVG string such an element renders to (Visualize.Chart.render/2 with root: true).

5. The SVG element layer

Visualize.SVG.Element predates the IR and remains the HEEx-facing struct: %Visualize.SVG.Element{tag, attrs, children, content} mirrors an SVG element one-to-one, with tag an SVG tag atom and attrs a map rendered as attributes. It implements String.Chars and, when Phoenix is loaded, Phoenix.HTML.Safe via Visualize.SVG.Renderer.render/1, so it interpolates into a template as an iolist without escaping. from_ir/1 is the bridge from the IR: the scene graph is built with Visualize.IR.Element, converted once, and embedded.

5.1 Visualize.SVG.Element

from_ir/1 maps element types to tags (:group → :g, :root → :svg, :clip_path → :clipPath, :linear_gradient → :linearGradient, :radial_gradient → :radialGradient, others unchanged), copies attrs, adds d from the path via Visualize.IR.Path.to_string/1, adds transform via Visualize.IR.Transform.to_string/1, converts every style key to its hyphenated SVG attribute name (:stroke_width → :"stroke-width" and so on) and every style atom value to its SVG keyword (:none → "none", :current_color → "currentColor", :start, :middle, :end, :butt, :round, :square, :miter, :bevel, :bold). Style attributes overwrite geometric attrs of the same name. A :root element gains xmlns="http://www.w3.org/2000/svg" and its view_box attr becomes the viewBox key (D-11, D-34); a :polyline/:polygon whose points is a list of {x, y} tuples has it formatted as Visualize.Backend.SVG formats points (spec/09 §2.6). Conversion is recursive over children. The result renders, through Visualize.SVG.Renderer, to exactly the markup Visualize.Backend.SVG.render_element/1 emits for the same IR (D-34); test/visualize/svg/bridge_test.exs holds both to it.

FunctionContract
Visualize.SVG.Element.new/1As new/2 with empty attrs.
Visualize.SVG.Element.new/2An element with the given tag and attrs map, no children, no content.
Visualize.SVG.Element.attrs/2Merges a map or keyword list of attrs over the existing attrs.
Visualize.SVG.Element.append/2Appends an SVG.Element, an IR.Element (converted with from_ir/1), or a list mixing both.
Visualize.SVG.Element.from_ir/1Converts an IR.Element tree as described above.
Visualize.SVG.Element.content/2Sets the text content.
Visualize.SVG.Element.svg/0As svg/1 with no attrs.
Visualize.SVG.Element.svg/1An :svg element with xmlns="http://www.w3.org/2000/svg" merged under the given attrs.
Visualize.SVG.Element.g/0As g/1 with no attrs.
Visualize.SVG.Element.g/1A :g element.
Visualize.SVG.Element.rect/0As rect/1 with no attrs.
Visualize.SVG.Element.rect/1A :rect element.
Visualize.SVG.Element.circle/0As circle/1 with no attrs.
Visualize.SVG.Element.circle/1A :circle element.
Visualize.SVG.Element.ellipse/0As ellipse/1 with no attrs.
Visualize.SVG.Element.ellipse/1An :ellipse element.
Visualize.SVG.Element.line/0As line/1 with no attrs.
Visualize.SVG.Element.line/1A :line element.
Visualize.SVG.Element.polyline/0As polyline/1 with no attrs.
Visualize.SVG.Element.polyline/1A :polyline element.
Visualize.SVG.Element.polygon/0As polygon/1 with no attrs.
Visualize.SVG.Element.polygon/1A :polygon element.
Visualize.SVG.Element.path/0As path/1 with no attrs.
Visualize.SVG.Element.path/1A :path element.
Visualize.SVG.Element.text/0As text/1 with no attrs.
Visualize.SVG.Element.text/1A :text element.
Visualize.SVG.Element.tspan/0As tspan/1 with no attrs.
Visualize.SVG.Element.tspan/1A :tspan element.
Visualize.SVG.Element.defs/0As defs/1 with no attrs.
Visualize.SVG.Element.defs/1A :defs element.
Visualize.SVG.Element.clipPath/0As clipPath/1 with no attrs.
Visualize.SVG.Element.clipPath/1A :clipPath element.
Visualize.SVG.Element.linearGradient/0As linearGradient/1 with no attrs.
Visualize.SVG.Element.linearGradient/1A :linearGradient element.
Visualize.SVG.Element.radialGradient/0As radialGradient/1 with no attrs.
Visualize.SVG.Element.radialGradient/1A :radialGradient element.
Visualize.SVG.Element.stop/0As stop/1 with no attrs.
Visualize.SVG.Element.stop/1A :stop element.
Visualize.SVG.Element.title/0As title/1 with no attrs.
Visualize.SVG.Element.title/1A :title element.

Every constructor accepts attrs as a map or keyword list. The camel-case names (clipPath, linearGradient, radialGradient) are deliberate: they are the SVG tag names.

5.2 Visualize.SVG

The fluent builder over Visualize.SVG.Element. Attrs are accepted as keyword lists or maps.

FunctionContract
Visualize.SVG.new/0As new/1 with no attrs.
Visualize.SVG.new/1A root :svg element with xmlns and the given attrs.
Visualize.SVG.append/2Appends an SVG.Element child; or, when the second argument is a tag atom, as append/3 with no attrs.
Visualize.SVG.append/3Creates Element.new(tag, attrs) and appends it.
Visualize.SVG.append_all/2Appends each element of the list in order.
Visualize.SVG.group/0As group/1 with no attrs.
Visualize.SVG.group/1A :g element.
Visualize.SVG.translate/3A :g with transform="translate(x,y)" containing the given element or elements.
Visualize.SVG.render/1Visualize.SVG.Renderer.render/1: the tree as an iolist.
Visualize.SVG.to_string/1Visualize.SVG.Renderer.render_to_string/1.
Visualize.SVG.rect/0As rect/1 with no attrs.
Visualize.SVG.rect/1A :rect element.
Visualize.SVG.circle/0As circle/1 with no attrs.
Visualize.SVG.circle/1A :circle element.
Visualize.SVG.line/0As line/1 with no attrs.
Visualize.SVG.line/1A :line element.
Visualize.SVG.path/0As path/1 with no attrs.
Visualize.SVG.path/1A :path element.
Visualize.SVG.text/1As text/2 with no attrs.
Visualize.SVG.text/2A :text element with the given content and attrs.
Visualize.SVG.polyline/0As polyline/1 with no attrs.
Visualize.SVG.polyline/1A :polyline element.
Visualize.SVG.polygon/0As polygon/1 with no attrs.
Visualize.SVG.polygon/1A :polygon element.

5.3 Visualize.SVG.Path

A compatibility façade: every builder delegates to Visualize.IR.Path and operates on Visualize.IR.Path structs (there is no separate SVG.Path struct). New code SHOULD use Visualize.IR.Path directly.

FunctionContract
Visualize.SVG.Path.new/0Delegates to Visualize.IR.Path.new/0.
Visualize.SVG.Path.move_to/3Delegates to Visualize.IR.Path.move_to/3.
Visualize.SVG.Path.move_to_rel/3Delegates to Visualize.IR.Path.move_to_rel/3.
Visualize.SVG.Path.line_to/3Delegates to Visualize.IR.Path.line_to/3.
Visualize.SVG.Path.line_to_rel/3Delegates to Visualize.IR.Path.line_to_rel/3.
Visualize.SVG.Path.horizontal_to/2Delegates to Visualize.IR.Path.horizontal_to/2.
Visualize.SVG.Path.horizontal_to_rel/2Delegates to Visualize.IR.Path.horizontal_to_rel/2.
Visualize.SVG.Path.vertical_to/2Delegates to Visualize.IR.Path.vertical_to/2.
Visualize.SVG.Path.vertical_to_rel/2Delegates to Visualize.IR.Path.vertical_to_rel/2.
Visualize.SVG.Path.curve_to/7Delegates to Visualize.IR.Path.curve_to/7.
Visualize.SVG.Path.curve_to_rel/7Delegates to Visualize.IR.Path.curve_to_rel/7.
Visualize.SVG.Path.smooth_curve_to/5Delegates to Visualize.IR.Path.smooth_curve_to/5.
Visualize.SVG.Path.smooth_curve_to_rel/5Delegates to Visualize.IR.Path.smooth_curve_to_rel/5.
Visualize.SVG.Path.quad_to/5Delegates to Visualize.IR.Path.quad_to/5.
Visualize.SVG.Path.quad_to_rel/5Delegates to Visualize.IR.Path.quad_to_rel/5.
Visualize.SVG.Path.smooth_quad_to/3Delegates to Visualize.IR.Path.smooth_quad_to/3.
Visualize.SVG.Path.smooth_quad_to_rel/3Delegates to Visualize.IR.Path.smooth_quad_to_rel/3.
Visualize.SVG.Path.arc_to/8Delegates to Visualize.IR.Path.arc_to/8.
Visualize.SVG.Path.arc_to_rel/8Delegates to Visualize.IR.Path.arc_to_rel/8.
Visualize.SVG.Path.close/1Delegates to Visualize.IR.Path.close/1.
Visualize.SVG.Path.empty?/1Delegates to Visualize.IR.Path.empty?/1.
Visualize.SVG.Path.command_count/1Delegates to Visualize.IR.Path.command_count/1.
Visualize.SVG.Path.concat/1Delegates to Visualize.IR.Path.concat/1.
Visualize.SVG.Path.append/2Delegates to Visualize.IR.Path.append/2.
Visualize.SVG.Path.transform/2Delegates to Visualize.IR.Path.transform/2.
Visualize.SVG.Path.to_string/1Delegates to Visualize.IR.Path.to_string/1, the one d serialisation (D-12).

5.4 Visualize.SVG.Renderer

Serialises an SVG.Element tree. Attributes with a nil value are omitted; the rest are emitted sorted by their SVG name from attribute_name/1, values escaped (&, ", <, >); text content is escaped (&, <, >); child-less, content-less circle, ellipse, line, path, polygon, polyline, rect, image, use, stop, feDropShadow and feGaussianBlur elements are self-closing. The iolist form is what Phoenix.HTML.Safe returns. Visualize.Backend.SVG renders its tags, attributes and text through the same functions, so the two serialisers cannot drift (D-34).

FunctionContract
Visualize.SVG.Renderer.render/1The element tree as an iolist.
Visualize.SVG.Renderer.render_to_string/1render/1 converted to a binary.
Visualize.SVG.Renderer.attribute_name/1The SVG attribute name for a key (D-11): a snake_case atom hyphenates (:stroke_width → "stroke-width", :clip_path → "clip-path") unless it is one of the camel-cased SVG attributes, which map to their spelling — :view_box → "viewBox", :preserve_aspect_ratio, :gradient_units, :gradient_transform, :pattern_units, :pattern_transform, :clip_path_units, :mask_units, :std_deviation (stdDeviation), :marker_width, :marker_height, :marker_units, :ref_x, :ref_y, :spread_method, :text_length, :length_adjust; an atom already camel-cased (:viewBox) and a binary key pass through unchanged.