Math in Labels

Any label (plot title, axis labels, TextPlot markdown bodies) may embed math inside $...$ using LaTeX-ish syntax. There are two rendering tiers, chosen by how you build kuva:

  • Lookup tier (always available, zero dependencies): math is lowered to inline Unicode text at render time, in every backend including the terminal. The tables below show its output.

  • Typst tier (feature typst-math): the whole label is typeset by the Typst compiler (linked as a library, no external binary) and embedded in SVG/PNG/PDF output as real 2-D math: stacked fractions, radicals with vinculum, large operators, math italic. The terminal always uses the lookup tier (a character grid can't hold typeset math).

    typst-math is opt-in and is not part of full: it pulls a large dependency tree (the Typst compiler) and needs Rust 1.89 or newer, so you enable it explicitly with --features typst-math. It is also what separates the two pre-built CLI downloads on the releases page, because linking the Typst compiler roughly triples the archive size: kuva-<version>-<target> ships the lookup tier only, and kuva-<version>-<target>-typst adds this tier. Take the -typst download if you want typeset math, since a downloaded binary has no way to opt in afterwards.

The same $...$ source drives both tiers, so code never changes; only the fidelity of the output does.

#![allow(unused)]
fn main() {
Layout::new((0.0, 3.0), (0.0, 10.0))
    .with_x_label("$\\log_2$ fold change")
    .with_y_label("$-\\log_{10}(p)$")
    .with_title("Differential expression ($\\alpha = 0.05$)");
}

A literal dollar sign is written \$. A $ without a closing partner is left untouched as plain text.

Quick examples (lookup tier)

InputOutput
$\sigma^2$σ²
$x_i$xᵢ
$\mu \pm \sigma$μ ± σ
$a \leq b \cdot c$a ≤ b · c
$\frac{a+b}{c}$(a+b)/c
$\sqrt{x^2+y^2}$√(x²+y²)
$\sum_{i=1}^{n} x_i$∑ᵢ₌₁ⁿ xᵢ
$-\log_{10}(p)$-log₁₀(p)
$\sin(\theta)$sin(θ)
$\exp(-t)$exp(-t)
$f \circ g$f ∘ g

Supported syntax

Greek letters

Both cases: \alpha…\omega (lowercase) and \Gamma, \Delta, \Theta, \Lambda, \Xi, \Pi, \Sigma, \Phi, \Psi, \Omega (uppercase). Variants: \varepsilon → ε, \varphi → φ.

Operators, relations, arrows

\pm ±, \mp ∓, \times ×, \cdot ·, \div ÷, \circ ∘, \leq ≤, \geq ≥, \neq ≠, \approx ≈, \equiv ≡, \sim ∼, \propto ∝, \ll ≪, \gg ≫, \in ∈, \notin ∉, \subset ⊂, \cup ∪, \cap ∩, \infty ∞, \partial ∂, \nabla ∇, \degree °, \angle ∠, \forall ∀, \exists ∃, \ldots …, \cdots ⋯, \sum ∑, \prod ∏, \int ∫, \to/\rightarrow →, \leftarrow ←, \Rightarrow ⇒, \Leftarrow ⇐, \leftrightarrow ↔.

Operator names

Standard LaTeX function names pass through as plain text; their arguments and subscripts lower normally:

CommandOutputTypical use
\loglog$\log_{10} x$ → log₁₀ x
\lnln$\ln(n)$ → ln(n)
\expexp$\exp(-t)$ → exp(-t)
\sinsin$\sin(\theta)$ → sin(θ)
\coscos$\cos(\phi)$ → cos(φ)
\tantan$\tan(\alpha)$ → tan(α)
\arcsinarcsin$\arcsin(x)$ → arcsin(x)
\arccosarccos
\arctanarctan
\minmin$\min(a,b)$ → min(a,b)
\maxmax
\limlim$\lim_{x \to 0}$ → lim_(x → 0)
\limsuplim sup
\liminflim inf
\supsup
\infinf(infimum, not infinity: use \infty for ∞)
\argarg
\detdet
\dimdim
\kerker
\gcdgcd
\lcmlcm
\PrPr
\degdeg

Superscripts and subscripts

^ and _ take a single character or a {...} group. These are all-or-nothing: every character in the group must have a Unicode super/subscript form, otherwise the whole group falls back to a clean x^(…) or x_(…), never a half-substituted mix.

x^2       → x²         (digit, maps cleanly)
x^{2n}    → x²ⁿ        (both have superscript forms)
x^{2q}    → x^(2q)     (q has no superscript: clean fallback)
x_i       → xᵢ
x_{i+1}   → xᵢ₊₁

Braceless command operands work too: x^\alpha → x^(α), x_\beta → x_(β).

Fractions and radicals

\frac{a}{b} → a/b; multi-term parts are parenthesised: \frac{a+b}{c} → (a+b)/c. Rendering is always inline: the output is plain text that flows anywhere a label can go, including rotated y-axis titles and terminal grids.

\sqrt{x} → √x, \sqrt{x+y} → √(x+y), \sqrt[3]{x} → ³√x.

Typst tier

With the typst-math feature, $...$ labels upgrade automatically from the inline Unicode above to real typeset math. A compile failure never breaks a plot: the label falls back to the lookup tier and a one-time warning names the expression and Typst's hint.

Typst math is not LaTeX. The $...$ body is LaTeX-flavoured for familiarity, but in Typst a multi-letter run is a single identifier: mc is not m × c, so $E = mc^2$ fails to compile (and falls back to the lookup tier). Write the factors separated:

$E = m c^2$      ✓
$E = mc^2$       ✗  (lookup tier + a one-time warning)

Math in TextPlot bodies

$...$ inside a markdown TextPlot body is spliced into the wrapped paragraph as a typeset fragment: kuva's own word-wrap treats each fragment as one unbreakable word, the surrounding words stay real (selectable) text, and a line holding a tall fragment (stacked fraction, radical) grows its leading so neighbouring lines never collide.

Math spliced into wrapped TextPlot body text

Emitting Typst markup (TypstBackend)

The typst-math feature typesets math into kuva's own output. The separate typst feature does the opposite: it emits a .typ source document that you compile yourself, so $...$ regions stay native Typst math and are typeset with the fonts of the surrounding document rather than kuva's bundled ones. That matters when a figure has to match the body text of a thesis or paper.

The feature has zero dependencies (it writes markup, it does not link the compiler) and is not part of full:

kuva = { version = "0.6", features = ["typst"] }
#![allow(unused)]
fn main() {
use kuva::backend::typst::TypstBackend;

let scene = render_multiple(plots, layout);
let typ = TypstBackend::default().render_scene(&scene);
std::fs::write("figure.typ", typ)?;
}

Then compile it to PDF, SVG or PNG:

typst compile figure.typ

The output is a complete document built on CETZ (pinned to a known-good version so a new CETZ release cannot break it). Primitive coverage is full: Path lowers to CETZ merge-path calls with arcs converted to cubic Beziers, so arrowheads, chord ribbons, sankey flows and venn outlines survive; clip regions map to Typst's box(clip: true), so quiver and network arrowheads and plot-area clipping round-trip correctly.

This backend is library-only. The CLI's -o accepts .svg, .png and .pdf only, so a .typ is produced by constructing a TypstBackend in Rust.

Plot examples

Generated by cargo run --features full,typst-math --example math, so these show the typst tier. Without typst-math the same code produces the lookup tier's inline-Unicode forms instead:

Scientific axes

The standard RNA-seq and GWAS axis pair, $\log_2$ fold change / $-\log_{10}(p)$:

log2 fold change vs -log10(p) axes

Trigonometric labels

$\sin(\theta)$ in the title and y-axis label:

sin(theta) line plot

Exponential decay

$\exp(-t)$ on the y-axis:

exp(-t) decay curve

Greek letters and symbols

$\mu \pm \sigma$ on the x-axis:

Greek letters in an axis label

Superscripts

$x^2 + y^2 = r^2$ as the x-axis label:

Superscripts in an axis label

Fractions

$\frac{a+b}{c}$ → (a+b)/c:

Fraction in an axis label

Square root

$\sqrt{x^2+y^2}$ → √(x²+y²):

Square root in an axis label

Summation with limits

$\sum_{i=1}^{n} x_i$ → ∑ᵢ₌₁ⁿ xᵢ:

Summation with limits

Quadratic formula

$x = \frac{-b \pm \sqrt{b^2 - 4 a c}}{2 a}$. The lookup tier lowers this to x = (-b ± √(b² - 4 a c))/(2 a):

Quadratic formula in an axis label

Mixed text and math

Mixed text and math in labels

Rotated y-axis title

Energy $E = m c^2$. Math works in rotated labels too:

Math in a rotated y-axis title

CLI

Math works in any label flag; no extra flags needed:

kuva scatter data.tsv \
    --x log2fc --y neg_log10_pvalue \
    --x-label '$\log_2$ fold change' \
    --y-label '$-\log_{10}(p)$' \
    --title 'Differential expression ($\alpha = 0.05$)' \
    -o volcano.svg

The same labels render in the terminal backend with --terminal; the lowered Unicode lands directly on the character grid.

Shell quoting

$ and \ are special characters in most shells. Single quotes are the simplest way to pass math labels literally: the shell treats everything between '...' as plain text:

--x-label '$\log_2$ fold change'   # single quotes: $ and \ are literal

If you need to interpolate a shell variable into a label that also contains math, mix quote styles:

SAMPLE="$(basename "$file" .tsv)"
--title '$\sigma^2$ for '"$SAMPLE"
#        ^^^^^^^^^^^^^^^^ literal  ^^^^^^^^^ variable expands

Or use double quotes and escape both $ and \:

--title "\$\\sigma^2\$ for $SAMPLE"
#        \$ → $,  \\ → \,  $SAMPLE → expands

On Windows (cmd.exe / PowerShell), $ and \ have no special meaning in double-quoted strings, so no escaping is needed:

kuva scatter data.tsv --x-label "$\log_2$ fold change"