Universe

Graph paper for Typst: plot functions, points, lines and vectors for math and physics homework, with grid steps, scale and cropping you choose. Written in plain Typst, with no dependencies.

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

#graph(
  x: (-3, 3), y: (-1, 6), scale: 8mm,
  fn(x => x * x - 1, [$y = x^2 - 1$]),
  points((-1, 0, $A$), (1, 0, $B$), (2, 3, $C$)),
  segment((-1, 0), (2, 3), extend: true, stroke: (dash: "dashed")),
)

Four graphs made with quadrille: a parabola with named points and a line through two of them; a velocity-time lab graph with measured points, a fitted line and a shaded area; a triangle with a force vector; and tan x with 2 sin x on ticks in steps of pi/2.

The rules

Everything follows the same few rules, so once you know them you can guess the rest:

a point (x, y); a named point is (x, y, $A$)
a range (from, to): the window x: / y: of a graph, the domain: of an element
axis options come as x-… and y-…; the plain name sets both: step: 1 = x-step: 1, y-step: 1
stroke: anything Typst’s stroke takes: red, 2pt, 2pt + red, (dash: "dashed")
label: the element’s entry in the legend
a trailing text fn(f, [$y = x^2$]), vector(a, b, $arrow(F)$): written on the graph next to the element; pos: picks the side (top, bottom + left …), auto finds a free spot
colors curves and points take the next palette color; helper lines (segment, vector, hline, vline) are ink; an area takes the color of the curve before it

graph

graph(..elements, options) draws a grid, axes and everything given to it. All options are optional.

option default
x, y auto the visible window (from, to): cropping. Everything outside is cut off. auto fits the data and rounds out to whole steps
step, x-step, y-step auto distance between grid lines and numbered ticks, in graph units (x-step: calc.pi / 2)
minor, x-minor, y-minor none extra grid lines between two numbered ones: minor: 5 gives millimetre paper with step: 1
scale, x-scale, y-scale auto the length of one unit on paper: scale: 1cm. Takes priority over width / height
width, height 10cm, auto the size of the gridded area. height: auto keeps both axes at the same scale when that gives a sensible shape, else 0.65 × width. width: 100% fills the column
ticks, x-ticks, y-ticks auto none, or which values get numbers: (1, 2, 5), or your own text: ((1, $a$), (3, $b$))
format, x-format, y-format auto how numbers are written: auto, "pi" (π/2, π, 3π/2 …) or a function v => [#v s]
x-label, y-label none names at the ends of the axes: x-label: [$t$ (s)]
axes "origin" "origin": the axes cross at 0 (at the edge when 0 is out of view). "edge": bottom and left. none
grid true draw the grid
legend auto where the legend goes: auto (the emptiest corner), top + left …, or none
clip true cut curves off at the window
style (:) change the look, see Style

Numbers that would overlap are thinned out automatically; very large or small values share one power of ten, written once at the end of the axis (× 10⁻³).

Elements

element draws
fn(f) the graph of y = f(x). Options: domain, step or samples (how densely it is sampled, default 400 values), stroke, label
parametric(f) the curve t => (x, y) for t in domain (default 0 to 2π)
points(p, q, …) points; also points(data) with data an array of points, or points(f, step: 0.5): marks on a function. Options: mark ("dot", "circle", "square", "diamond", "triangle", "cross", "plus", none), size, connect: true (lines from each point to the next), close: true (also back to the first), fill (for closed shapes), stroke, label
segment(a, b) the segment from a to b; extend: true draws the whole line through a and b
vector(to), vector(from, to) an arrow; vector(to) starts at the origin
hline(y), vline(x) a dashed line across the graph (asymptotes, x = 2)
area(f), area(f, g) fills between f and the x axis, or between f and g, over domain. f and g may be numbers: area(4, domain: (0, 3))
annotate(p, body) text or math at a point; pos: top puts it above the point

vector and annotate are not called arrow and note on purpose: those names belong to math ($arrow(F)$) and to the music-note symbols, and importing quadrille must not hide them.

Where a function is undefined

Return none there and the curve gets a gap:

fn(x => if x != 0 { 1 / x })
fn(x => if x >= 0 { calc.sqrt(x) })

Jumps such as tan x crossing π/2 are found by themselves: no vertical line is drawn there.

Recipes

A physics lab graph (measurements joined by lines, a fitted line, mm paper):

#graph(
  x: (0, 10), y: (0, 50), x-step: 1, y-step: 10, minor: 5,
  width: 12cm, height: 7cm, axes: "edge",
  x-label: [$t$ (s)], y-label: [$v$ (m/s)],
  points((0, 0), (2, 11), (4, 19), (6, 31), (8, 40), (10, 49), connect: true, label: [measured]),
  fn(x => 4.9 * x, stroke: (dash: "dashed"), label: $v = 4.9 t$),
  area(x => 4.9 * x, domain: (0, 4), [$Delta x$]),
)

Data from a file: points(csv("data.csv").slice(1).map(r => r.map(float))).

Trigonometry: graph(x: (-2 * calc.pi, 2 * calc.pi), x-step: calc.pi / 2, x-format: "pi", fn(x => calc.sin(x))).

Piecewise, with open and filled end points:

#let (c1, c2) = (palette.at(0), palette.at(1))
#graph(x: (-3, 3), y: (-2, 4), scale: 8mm,
  fn(x => x + 2, domain: (-3, 0), stroke: c1), points((0, 2), mark: "circle", stroke: c1),
  fn(x => x * x, domain: (0, 3), stroke: c2), points((0, 0), stroke: c2),
)

Vectors:

#graph(x: (-1, 5), y: (-1, 4), scale: 1cm,
  vector((3, 2), $arrow(v)$),
  vector((3, 2), (4, 3.5), $arrow(F)$, stroke: red),
)

Style

graph(style: (...)) changes the look; default-style lists every key:

key default
palette eight colors, chosen to stay distinguishable with color blindness
ink axes, helper lines, text
line, helper, axis thickness of curves, helper lines, axes
grid, minor-grid strokes of the grid lines
tick, tick-label, axis-label tick length, and the size of numbers and axis names
arrow, overhang arrowhead length, how far the axes stick out
mark, area, gap, halo point size, how see-through areas are, text distance, background behind texts

For a house style, set the defaults once with Typst’s own .with:

#let graph = graph.with(scale: 1cm, minor: 2, style: (grid: 0.4pt + luma(190)))

Contributing

Issues and pull requests are welcome at github.com/ngelkind/quadrille. The files in tests/ there compile every feature:

for f in tests/*.typ; do typst compile --root . "$f" /tmp/out.pdf || echo "FAILED: $f"; done

Author

Made by Nachum Getzel Elkind (@ngelkind) for math and physics homework. If quadrille helps you, a star on GitHub or a mention is appreciated.

Released under the MIT licence.