plotkit
Publication-quality plots in three lines of Rust.
Documentation · User Guide · Gallery · Benchmarks
Rust deserves charts that belong in a journal paper — without shelling out to Python, booting a browser, or hunting for a system font that exists on your laptop but not on CI.
plotkit is a pure-Rust plotting library with a matplotlib-shaped API. Add one crate, write three lines, and get an anti-aliased, professionally themed figure as a PNG, SVG, or PDF. No GPU. No subprocess. No system dependencies. Identical bytes on Linux, macOS, and Windows.
plot?;
title;
savefig?;
Why plotkit
- 🎨 It looks good by default. A hand-tuned theme, the Tableau-10 palette, the Talbot–Lin–Hanrahan tick algorithm, and an automatic layout that never clips a label.
- ⚡ It's fast. A 10,000-point line renders to PNG in ~11 ms; a decimated 1,000,000-point scatter in ~34 ms — all on the CPU. See BENCHMARKS.md.
- 📦 It's self-contained. One static binary, zero runtime dependencies. The Inter font is embedded, so text renders deterministically everywhere — no font lookup to fail at 2 AM.
- 🐍 It's familiar. If you know matplotlib, you already know plotkit:
Figure,Axes,ax.plot(...),ax.scatter(...),fig.savefig(...). - 🔌 It's composable. Plot straight from Polars
DataFrames andndarrayarrays, render inline in Jupyter (evcxr), or draw to an HTML canvas via WebAssembly.
Installation
[]
= "1.0"
= { = "1.0", = ["svg", "pdf", "jupyter", "ndarray", "polars"] }
| Feature | Description |
|---|---|
png |
PNG rasterization (default) |
svg |
SVG vector output (default) |
pdf |
Print-ready PDF vector output |
jupyter |
Inline display in Evcxr notebooks |
ndarray |
Plot directly from ndarray::Array1 |
polars |
Plot directly from Polars Series / DataFrame |
Quick start
The pyplot facade is perfect for scripts — it operates on a current figure, just like
matplotlib.pyplot:
use *;
let x: = .map.collect;
let y: = x.iter.map.collect;
plot?;
title;
xlabel;
ylabel;
savefig?;
For full control, drop down to the Figure / Axes model:
use *;
let mut fig = with_size;
let ax1 = fig.add_subplot;
ax1.plot?.label.color;
ax1.plot?.label.color;
ax1.set_title;
ax1.legend;
let ax2 = fig.add_subplot;
ax2.scatter?.marker;
ax2.set_title;
fig.tight_layout;
fig.save?;
Chart types
Sixteen chart types, one familiar API:
plot — line |
scatter |
bar / barh |
bar_group |
hist |
fill_between |
step |
stem |
boxplot |
violin |
errorbar |
heatmap |
pie |
contour / contourf |
hexbin |
polar / waterfall |
Browse the gallery of 55+ runnable examples — each is a complete program:
DataFrames & arrays
Polars and ndarray are first-class inputs. Any Series, Array1, or slice flows through
the same IntoSeries trait — zero glue code:
use Array1;
let x = linspace;
ax.plot?;
Polars users get a pandas-style df.plot() accessor with categorical grouping:
use *;
use DataFramePlotExt;
df.plot
.color_by // one labelled series per category
.line?
.save?;
Themes, colormaps & scales
fig.set_theme; // default · dark · seaborn · ggplot · publication · nature · solarized
ax.scatter?.cmap.c; // 14 perceptual colormaps
ax.set_yscale; // Linear · Log10 · SymLog
ax.set_xlim;
ax.set_xticks;
ax.invert_yaxis;
ax.grid;
let ax2 = fig.twinx; // independent secondary y-axis
ax2.plot?;
ax.annotate;
ax.text;
Output formats
| Format | Method | Notes |
|---|---|---|
| PNG | fig.save("out.png") |
CPU-rasterized, deterministic |
| SVG | fig.save("out.svg") |
Scalable vector (svg feature) |
fig.save("out.pdf") |
Print-ready vector (pdf feature) |
|
| Bytes | fig.to_png_bytes() / fig.to_pdf_bytes() |
Embed or stream |
| String | fig.to_svg_string() |
Web / templating |
Format is selected from the file extension — one method, every backend.
Performance
Pure-CPU rendering, measured with Criterion (medians, AMD Ryzen AI 7 350):
| Workload | Measured |
|---|---|
| 10k-point line → PNG | 10.9 ms |
| 100k-point line → PNG (auto LTTB) | 13.7 ms |
| 1M-point scatter → PNG (decimated) | 33.7 ms |
| Figure creation (no render) | 434 ns |
Automatic LTTB decimation keeps the rasterizer working on a screen-sized point count no matter how large the input. Full methodology and TRD targets in BENCHMARKS.md.
Render anywhere — including the browser
The Renderer trait is the only seam between plot logic and pixels, so the same figure code
targets every backend. plotkit-render-wasm draws to an HTML5 canvas via WebAssembly:
A ready-to-serve demo lives in web/.
Architecture
plotkit is a focused multi-crate workspace:
| Crate | Purpose |
|---|---|
plotkit |
Umbrella crate — pyplot facade + save |
plotkit-core |
Figure, Axes, artists, the Renderer trait |
plotkit-render-skia |
PNG backend (tiny-skia + cosmic-text) |
plotkit-render-svg |
SVG backend |
plotkit-render-pdf |
PDF backend (printpdf) |
plotkit-render-wasm |
WASM Canvas2D backend |
plotkit-ndarray · plotkit-polars |
DataFrame / array integration |
Core logic never depends on a concrete backend — adding a new output format means implementing one trait.
Status & roadmap
v1.0 — stable. The public API is semver-stable; signatures shipped in 1.0 only grow, never break. Shipping today: 16 chart types, PNG/SVG/PDF, 7 themes, 14 colormaps, log/symlog scales, twin axes, subplots, Polars + ndarray, Jupyter inline rendering, and a WASM backend.
| Version | Focus |
|---|---|
| v1.1 | Animation / frame sequences |
| v1.2 | 3D surface plots |
| v1.x | Interactive widgets; optional GPU (Vello) backend behind the Renderer trait |
Contributing
Bug reports, feature requests, and pull requests are all welcome — start with
CONTRIBUTING.md. Every visual change needs an insta golden-image
snapshot; run cargo insta review before committing.
License
Licensed under either of MIT or Apache-2.0 at your option.