Skip to content

Grammar of graphics

A chart in insomni-plot is a spec, not a sequence of drawing calls. You declare what the data means — which columns map to which visual channels, which marks to draw, how the axes look — and the library compiles that into insomni layers on each frame. If you’ve used ggplot2 or Observable Plot, this mental model is already yours.

plot(spec) returns a Chart<T>. Every builder method returns a new Chart with the addition recorded; the original is untouched. Specs are cheap to fork, share, and re-derive — branch from a base, layer on variants, hand them to faceting — none of it mutates anything.

import { plot, point, line } from "insomni-plot";
const base = plot<Row>({ data }).axes({ x: { title: "t" } });
const withPoints = base.layer(point({ x: "t", y: "v" }));
const withBoth = withPoints.layer(line({ x: "t", y: "v" }));
// `base` and `withPoints` are unchanged.
MethodPurpose
.layer(geom)Add one geom layer.
.layers(geoms)Add several geom layers at once.
.scale(channel, options)Configure a scale ("x", "y", "y2", "color", "size", "alpha", "shape", "borderStyle", "overlayGlyph").
.axes(spec)Per-axis options: { x?, y? }.
.title(spec)Title / subtitle. Accepts a string or { title?, subtitle? }.
.legend(spec | false)Configure or disable the auto-legend.
.annotate(spec)Add a free-form text / arrow / callout overlay. Accumulates.
.facet(spec)Split into a grid of panels by a key. Replaces any prior facet.
.coord(coord)Set the coordinate system (default coordCartesian()).
.theme(theme)Replace the active theme.
.fullBleed(spec)Bleed the plot panel to the canvas edge on one or more sides (per-edge or true for all four).
.padding(spec)Outer canvas inset before axes / legend / title. Same shape as PlotSpec.padding.
.mount(canvas, opts?)Bind to a canvas → MountedPlot.
.render(target)Compile + emit into externally owned layers.
.toSVG(opts?)Static SVGSVGElement export.
.toLayers(opts)Build the chart’s Layer[] (axis, marks, hud) for a headless GPU renderer (e.g. insomni-node). Requires a GlyphAtlas.
.toInteractiveSVG(opts)Simulated-hover/click SVG export — renders tooltip, crosshair, and emphasis without a GPU.

The terminal methods — .mount(), .render(), .toSVG(), .toLayers(), and .toInteractiveSVG() — produce output and are not chainable. All other methods return a new Chart and can be chained. See Mount, render & export.

plot<T>(spec) accepts:

FieldTypeNotes
datareadonly T[] | Signal<readonly T[]>Required. A reactive signal re-renders on change.
width / heightnumberCSS pixels. Auto-resize otherwise.
backgroundColorCanvas background.
paddingnumber | { all?, top?, right?, bottom?, left? } | { ratio: number }Outer canvas inset before axes / legend / title.
framePaddingnumber | { top?, right?, bottom?, left? }Inner slack between axes and data.
fullBleedboolean | { top?, right?, bottom?, left? }Bleed the plot panel to the canvas edge. true bleeds all four sides; an object bleeds only the named sides.
deviceGPUDeviceCan also be passed to .mount().
themeThemeInitial theme; replaceable via .theme().

Each frame, the chart compiles the spec into three insomni Layers. The sequence — implemented in pipeline.ts and shared by both the GPU mount loop and the SVG export path — is:

data ─▶ aes ─▶ scales ─▶ coord ─▶ geoms ─▶ layers
  1. aes — each geom’s channel mappings are resolved (Aes<T, V> → an accessor + metadata via resolveAes). Column / accessor / constant all normalize to (row, index) => value.
  2. scales — domains are inferred from the materialized channel values (unless you pass an explicit domain to .scale()), and each channel gets a scale function value → pixel (positions) or value → Color (color). Scale type is picked from the data type (numbers → linear, strings → band, dates → time) unless overridden.
  3. coord — the active Coord decides how scaled plot-frame pixels map to layer pixels. coordCartesian() is the identity; coordPolar / coordRadial project (θ, r). See Axes & coordinates.
  4. geoms — each geom’s compile(ctx) emits insomni mark builders (rects, point clouds, polylines, glyphs) into the marks layer; axes / legend / title go to the axis and hud layers.
  5. layers — the three layers (axis, marks, hud) are rendered by Renderer2D.

The three layers map onto the RenderTarget you pass to .render(), and onto the axisLayer / marksLayer / hudLayer escape hatches on MountedPlot.

Each geom is compiled against a CompileContext that carries the resolved scales, the active theme, the panel’s plot frame, the glyph atlas, and interaction state (hovered, selected, hidden, the active transition). Geoms read this to produce their marks and, optionally, hit-tests for tooltips and selection.

This contract is internal — you author charts by composing geoms, not by implementing compile yourself — but it’s worth knowing about, because it explains why a single declarative spec can drive hover, selection, transitions, and SVG export from one source of truth.

  • Geoms — the mark vocabulary and each geom’s aesthetics.
  • Scales & aesthetics — channels, scale types, domains, and legends.