A maintainer-facing map of how the library is wired.
For naming conventions see GLOSSARY.md.
For workflow see CONTRIBUTING.md.
Data flows forward only. No stage reaches back into an earlier one.
data ──▶ stat ──▶ position ──▶ scale ──▶ coord ──▶ facet ──▶ theme ──▶ render
Entry points trace the same path:
lib.typis the public facade. It re-exports every user-facing function (plot,aes,annotate,geom-*,stat-*,scale-*,scales,theme-*,labels,guides,compose, …). Internal helpers stay unexported and are_-prefixed.src/plot.typis the grammar entry point. It normalises data, flattens the aesthetic mapping, merges per-layer mappings, and builds the spec dict.src/render.typorchestrates rendering. It trains scales, applies stats/positions/coord/facet/theme/labels, then dispatches to the canvas builders undersrc/render/.
| Directory | Purpose |
|---|---|
src/geom/ |
Geometric layers; each exports a constructor (via make-layer) and a draw(layer, ctx). Shared draw scaffolding for geom families (grouped-path, errorbar-draw, ref-line, label-draw) lives here too. |
src/stat/ |
Statistical transforms; dispatched by src/stat/apply.typ. |
src/position/ |
Position adjustments (stack, dodge, fill, jitter, …); dispatched by src/position/apply.typ. |
src/scale/ |
Aesthetic-agnostic scales. constructors.typ returns family-tagged stubs. bind.typ validates stub arguments against per-builder key tuples (sync-checked by tools/typstdoc/scale_keys.lua), then dispatches (aesthetic, name) to family-file builders (continuous, discrete, colour, date, size, …). train.typ trains domains. |
src/coord/ |
Coordinate systems (cartesian, fixed, flip, radial, transform). |
src/facet/ |
Faceting (grid, wrap) and strip labellers. |
src/guide/ |
Legend and axis configuration, legend-symbol drawing, the entry table (entry.typ) that says what a guide annotates, the parts that draw it (primitive/ and gizmo/, laid out by compose.typ on the grid geometry in grid.typ), and the context they draw under (gctx.typ). Imports only from deps.typ and utils/, never from render/ or scale/; anything here that needs a scale, a theme, or a text measurement takes a closure or a stamped number instead. |
src/theme/ |
Theme structure, named themes, element builders, global state. |
src/render/ |
Rendering pipeline: domain, facet layout, panel draw, canvas, chrome, legend renderer (render/legend.typ), and the axis band (render/axis-parts.typ), which the chrome stage measures and the panel draws from one stack of guide primitives. |
src/utils/ |
Shared leaf helpers: types, formatting, colour, late-binding, binning, errors, rotated-label geometry (label-geometry.typ). No cetz drawing lives here. |
src/datasets/ |
Built-in example datasets (economics, mpg, penguins). |
Design tenets worth knowing before editing:
- Spec dict as the intermediate form.
plot()returns a spec socompose()can defer rendering and hoist shared legends across panels. - Late binding. Aesthetic values can resolve at later stages (
after-stat,after-scale,from-theme,stage). Seesrc/utils/late-binding.typ. - Per-facet stat re-training. Stats such as smooth, bin, and boxplot re-run per panel to respect facet semantics.
- Single CeTZ import. Only
src/deps.typimports third-party packages.tools/typstdocrejects@preview/*imports elsewhere undersrc/. - Keyed-by-aesthetic plot inputs.
scales()(src/scales.typ),guides()(src/guides.typ), andlabels()(src/labels.typ) each build a dict keyed by aesthetic and feed it toplot(). A later entry for the same aesthetic wins.expand-limits(src/limits.typ) andannotate(src/annotate.typ) are top-level shortcuts.src/aes-keys.typsingle-sources theAES-KEYSchannel list.
- A geom. Copy the closest existing
src/geom/*.typ, build the layer withmake-layerfromsrc/layer.typ, and export the constructor throughlib.typ. Provide a legend symbol viasrc/guide/draw-key.typ/src/guide/draw-marker.typ. - A guide. Write a builder in
src/render/legend.typthat returns the stack the guide is, over the primitives insrc/guide/primitive/laid out bysrc/guide/compose.typ, and register it under its kind in the_NODE-BUILDERStable beside the others. The builder is the one place that measures text and reads the theme; a primitive takes the stamped numbers and the closures on the guide context (src/guide/gctx.typ) instead. The width, the height, the reserved slot and the draw all read the recordcompose.typlaid out, so none of them branches on a kind: a guide that paints a colour bar is reserved for by carrying that part, not by being named one. Merging is the one decision left that reads the kind, incan-merge-cross-panel, because whether two guides describe the same scale depends on what a kind compares. An axis is the same shape, built bysrc/render/axis-parts.typfor a cartesian side and bysrc/render/panel-radial.typfor the angular and radial ones. - A stat. Add
src/stat/<name>.typand register it insrc/stat/apply.typ. - A position. Add
src/position/<name>.typand register it insrc/position/apply.typ. - A scale. Add or extend the internal builder in the relevant family file (
src/scale/continuous.typ,src/scale/colour.typ, …). Register its family name under each supported aesthetic in thesrc/scale/bind.typdispatch table. Expose a publicscale-<name>constructor insrc/scale/constructors.typ, which returns_stub(family, args). Re-export it throughlib.typ.
Never inline a panic string.
Route every validation through src/utils/errors.typ, which centralises the grammar:
<scope>: <problem>; got <repr(value)>. <hint>
fail(scope, problem, hint: none)for bespoke messages.fail-enum(scope, name, value, valid, hint: none)for "must be one of …".fail-type(scope, name, value, expected, hint: none)for type mismatches.fail-range(scope, name, value, lo, hi, lo-open: …, hi-open: …, hint: none)for numeric intervals.check(cond, scope, problem, hint: none)replaces a bareassert.
Typst cannot catch a panic, so the message builders (error-text, enum-text, type-text, range-text) are pure and unit-tested in tests/unit/test-errors.typ.
tests/unit/holds pure-function assertions compiled bytypst compile. Add unit tests here for any new helper.tests/visual/holds PNG snapshot goldens checked viatools/snapshot/run.lua. Goldens are CPU-architecture sensitive. Refresh them through the Linux snapshot workflow, not locally.- Run
tools/check.shto mirror CI (compiles every unit test and example;--snapshotadds the visual check). - Consult
GLOSSARY.mdbefore introducing any new short identifier.