Skip to content

Latest commit

 

History

History
96 lines (77 loc) · 7.57 KB

File metadata and controls

96 lines (77 loc) · 7.57 KB

Gribouille architecture

A maintainer-facing map of how the library is wired. For naming conventions see GLOSSARY.md. For workflow see CONTRIBUTING.md.

Pipeline

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.typ is 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.typ is the grammar entry point. It normalises data, flattens the aesthetic mapping, merges per-layer mappings, and builds the spec dict.
  • src/render.typ orchestrates rendering. It trains scales, applies stats/positions/coord/facet/theme/labels, then dispatches to the canvas builders under src/render/.

Module map

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 so compose() 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). See src/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.typ imports third-party packages. tools/typstdoc rejects @preview/* imports elsewhere under src/.
  • Keyed-by-aesthetic plot inputs. scales() (src/scales.typ), guides() (src/guides.typ), and labels() (src/labels.typ) each build a dict keyed by aesthetic and feed it to plot(). A later entry for the same aesthetic wins. expand-limits (src/limits.typ) and annotate (src/annotate.typ) are top-level shortcuts. src/aes-keys.typ single-sources the AES-KEYS channel list.

Adding things

  • A geom. Copy the closest existing src/geom/*.typ, build the layer with make-layer from src/layer.typ, and export the constructor through lib.typ. Provide a legend symbol via src/guide/draw-key.typ / src/guide/draw-marker.typ.
  • A guide. Write a builder in src/render/legend.typ that returns the stack the guide is, over the primitives in src/guide/primitive/ laid out by src/guide/compose.typ, and register it under its kind in the _NODE-BUILDERS table 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 record compose.typ laid 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, in can-merge-cross-panel, because whether two guides describe the same scale depends on what a kind compares. An axis is the same shape, built by src/render/axis-parts.typ for a cartesian side and by src/render/panel-radial.typ for the angular and radial ones.
  • A stat. Add src/stat/<name>.typ and register it in src/stat/apply.typ.
  • A position. Add src/position/<name>.typ and register it in src/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 the src/scale/bind.typ dispatch table. Expose a public scale-<name> constructor in src/scale/constructors.typ, which returns _stub(family, args). Re-export it through lib.typ.

Error conventions

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 bare assert.

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.

Testing and naming

  • tests/unit/ holds pure-function assertions compiled by typst compile. Add unit tests here for any new helper.
  • tests/visual/ holds PNG snapshot goldens checked via tools/snapshot/run.lua. Goldens are CPU-architecture sensitive. Refresh them through the Linux snapshot workflow, not locally.
  • Run tools/check.sh to mirror CI (compiles every unit test and example; --snapshot adds the visual check).
  • Consult GLOSSARY.md before introducing any new short identifier.