Gribouille: A Grammar of Graphics for Typst

mcanouil1 pts0 comments

Gribouille: a Grammar of Graphics for Typst – Mickaël CANOUIL

Skip to main content

1 Introduction

Typst has grown from a curious newcomer to a credible alternative for scientific writing, reports, and slide decks. What it still lacked, until today, was a real grammar of graphics: a way to declare a figure with the same vocabulary you would reach for in ggplot2, then have it draw, lay out, and align with the rest of the document.

Gribouille (French for scribble) is the first cut of that library. Version 0.1.0 lands on Typst Universe with this announcement, modelled closely on ggplot2, drawing on top of cetz, and shipped with a documentation site that is itself a Quarto project rendered by the quarto-typst-render extension. Every figure in this post is a real, freshly compiled plot.

NoteAt a glance

Gribouille v0.1.0 on Typst Universe: #import "@preview/gribouille:0.1.0": *.

A broad library of geoms and stats, scales spanning every aesthetic, a range of built-in themes, and full facet support.

compose() orchestrates several plots into a single figure with a shared, hoisted legend, fed by a new defer: true flag on plot().

quarto-typst-render v0.13.1 ships typst_define(), the Python and R helper that streams arbitrary values, scalars, lists, dictionaries, and full data frames alike, straight into Typst code blocks.

2 Why a grammar of graphics for Typst

Until now, producing a figure inside a Typst document meant taking a detour through another language. You would leave Typst to call out to ggplot2, matplotlib, or plotnine, save a PNG or SVG, and import it back. That works, but it splits the toolchain in two: one font system on the page, another inside the figure; one colour palette in the report, another in the chart; one render pass that knows about the layout, another that does not.

cetz already gives Typst a robust low-level drawing API, but the declarative layer that turns a dataset, a mapping, and a few layers into a publication-quality figure was missing. That is exactly the gap Gribouille fills.

One toolchain. Same fonts, same palette, same render pass, from data to PDF.

The pay-off is a single toolchain. Give a plot an alt: description and Gribouille wraps the result in a Typst figure element with kind: "gribouille-plot", so you can attach show rules, captions, or cross-references in one place; with or without it, every plot shares the document’s fonts, palette, and geometry. The YAML front-matter of this very post wires its background: and foreground: to the site’s light and dark colours, and every figure below reacts automatically when you toggle the theme. Compilation is deterministic, offline-friendly, and produces a PDF straight out of Typst with no Python or R runtime required.

3 A respectful nod to ggplot2

A short personal aside. I have been using ggplot2 since almost its first release, and a large part of how I think about data visualisation comes from that work. This release would not exist without Hadley Wickham’s original vision, nor without the people carrying it forward today, in particular Thomas Lin Pedersen and Teun van den Brand, and the wider community of contributors who keep shaping the package through to the recent v4 release.

Gribouille is, in many ways, a port of ggplot2’s vocabulary into Typst. The names line up: plot() instead of ggplot(), geom-point() for geom_point(), facet-wrap(), scale-colour-*(), theme-minimal(), and so on. Where the two part ways, it is because Typst is a different language, with no lazy evaluation, no plot object you can mutate after the fact, and no ggsave() output pipeline. Column names are plain strings. Per-aesthetic type coercion is done inline with as-factor() and as-numeric(). These are opinionated departures, but the mental model is the same one you already have.

4 Build a penguins plot, one layer at a time

The library ships the Palmer Penguins dataset under the penguins symbol. The walk-through below builds a single figure end to end, one grammar concept at a time, with brief notes on alternatives at each step. It is a tighter version of the Get Started page on the documentation site.

Every plot you build composes the same pieces. plot() wraps a stack of layers, each sheet sitting on the one below, from data and mapping at the foundation up through theme and labels.

The post’s YAML points the typst-render extension at a one-line preamble file so every {typst} block has the library in scope:

extensions:<br>typst-render:<br>preamble: _preamble.typ

And _preamble.typ contains:

#import "@preview/gribouille:0.1.0": *

You would write the same #import once at the top of your own Typst document.

4.1 Data and aesthetics

Every plot starts with a dataset and an aes() mapping that binds column names to visual channels. At a minimum, geom-point() needs x and y.

#plot(<br>data: penguins,<br>mapping: aes(x: "flipper-len", y: "body-mass"),<br>layers: (geom-point(size: 2pt),),<br>width: 12cm,<br>height: 9cm,

The plot-level mapping is inherited by every...

typst plot gribouille figure ggplot2 render

Related Articles