Ruviz
High-performance 2D plotting library for Rust combining matplotlib's ease-of-use with Makie's performance.
Install / Use
/learn @Ameyanagi/RuvizREADME
ruviz
High-performance 2D plotting library for Rust combining matplotlib's ease-of-use with Makie's performance.
Release Notes
Package Surfaces
- Rust crate:
ruviz - GPUI adapter crate:
ruviz-gpui - Raw Rust wasm bridge:
ruviz-web - Python package:
ruvizinpython/ - npm package:
ruvizinpackages/ruviz-web/
Release-facing media and generated docs now use the documented layout in
docs/BUILD_OUTPUTS.md. Regenerate the full release docs
surface with make release-docs on the release docs branch.
Quick Start
use ruviz::prelude::*;
let x: Vec<f64> = (0..50).map(|i| i as f64 * 0.1).collect();
let y: Vec<f64> = x.iter().map(|&x| x * x).collect();
Plot::new()
.line(&x, &y)
.title("Quadratic Function")
.xlabel("x")
.ylabel("y = x^2")
.save("plot.png")?;
Need typeset math labels? See Typst Text Mode below.

Features
🛡️ Safety & Quality
- Zero unsafe in public API
- Strong type system prevents runtime errors
- Comprehensive error handling with
Resulttypes - Memory-safe by design
📊 Plot Types
Basic: Line, Scatter, Bar, Histogram, Box Plot, Heatmap Distribution: Violin, KDE, ECDF Composition: Pie, Donut Continuous: Contour Polar: Polar Plot, Radar Chart Error: Error Bars (symmetric/asymmetric)
🎨 Publication Quality
- High-DPI export: 72, 96, 300, 600 DPI for print
- Multiple formats: PNG, SVG, and PDF (with the
pdffeature) - Professional themes: Light, Dark, Publication, Seaborn-style
- Custom styling: Colors, fonts, markers, line styles
- International text: Full UTF-8 support (Japanese, Chinese, Korean, etc.) with cosmic-text
⚡ Advanced Features
- Simple API: One-liner functions for quick plotting
- Parallel rendering: Multi-threaded for large datasets (rayon)
- GPU acceleration: Optional wgpu backend (experimental)
- Interactive plots: Optional desktop window integration on Linux, macOS, and Windows
- Mixed-coordinate insets: Embed polar, pie, and radar plots inside Cartesian figures
- Browser runtime: Experimental
ruviz-webadapter andruviznpm SDK forwasm32canvas rendering - Animation: GIF export with
record!macro and easing functions - Cross-platform: Linux, macOS, Windows, and experimental browser/wasm targets
Installation
Add to your Cargo.toml:
[dependencies]
ruviz = "0.4.9"
Feature Flags
Choose features based on your needs:
[dependencies]
ruviz = { version = "0.4.9", features = ["parallel", "simd"] }
| Feature | Description | Use When |
|---------|-------------|----------|
| default | ndarray + parallel | General use |
| parallel | Multi-threaded rendering | Large datasets |
| simd | Vectorized transforms | Performance-critical |
| animation | GIF animation export | Animated plots |
| gpu | GPU acceleration backend (experimental) | Opt-in GPU rendering |
| interactive | winit window support | Interactive plots |
| ndarray_support | ndarray types | Scientific computing |
| nalgebra_support | nalgebra vectors/matrices | Linear algebra workloads |
| polars_support | DataFrame support | Data analysis |
| pdf | PDF export | Publication output |
| typst-math | Typst text engine for all plot text | Math-heavy publication plots |
| full | Most bundled features (excludes HQ GIF/video extras) | Power users |
For minimal builds: default-features = false
Benchmark note: the current Rust feature-impact study in
docs/benchmarks/rust-feature-impact.md shows
parallel is the main default performance feature to care about, simd is situational, and
gpu should remain opt-in rather than a default build choice.
Experimental WASM Support
The core crate now compiles for wasm32-unknown-unknown with in-memory output helpers such as
Plot::render_png_bytes(), Plot::render_to_svg(), and Image::encode_png().
For browser interactivity, use the companion Rust bridge crate in
crates/ruviz-web and the public JS/TS SDK package
ruviz. The reference browser demo lives in
demo/web. Native file-path export helpers remain desktop-only.
Note:
ruvizautomatically registers a bundled browser fallback font for canvas sessions.- Custom browser fonts can still be added explicitly via
ruviz::render::register_font_bytes(...). - The current browser adapter provides main-thread canvas and OffscreenCanvas worker sessions, plus
web_runtime_capabilities()for feature diagnostics. - The Vite demo includes direct wasm export, main-thread interactivity, worker interactivity, and temporal signal playback plus Observable-driven updates.
- The JS workspace is Bun-first. Use
bun install,bun run build:web, andbun run test:webfrom the repo root for browser package and demo work.
Python Bindings
The repo now includes a mixed Python package in python built with uv, maturin,
and pyO3.
cd python
uv sync
uv run maturin develop
The Python package exposes a fluent ruviz.plot() builder for static export and uses the browser
runtime for notebook widgets. Outside Jupyter, plot.show() uses the native interactive window.
The published Linux wheel focuses on static rendering and notebook widgets; install from source on
Linux if you need the native desktop window there.
Standalone MkDocs docs and runnable examples live under python/docs and
python/examples.
Web SDK Docs
The browser-first JS/TS SDK in packages/ruviz-web now ships with
runtime examples under packages/ruviz-web/examples and a
standalone VitePress docs site under packages/ruviz-web/docs.
Typst Text Mode
Enable Typst text rendering:
[dependencies]
ruviz = { version = "0.4.9", features = ["typst-math"] }
Use .typst(true) on a plot to render all static text surfaces (titles, axis labels, ticks,
legend labels, and annotations) through Typst:
use ruviz::prelude::*;
let x: Vec<f64> = (0..50).map(|i| i as f64 * 0.1).collect();
let y: Vec<f64> = x.iter().map(|&v| (-v).exp()).collect();
Plot::new()
.line(&x, &y)
.title("$f(x) = e^(-x)$")
.xlabel("$x$")
.ylabel("$f(x)$")
.typst(true)
.save("typst_plot.png")?;
Notes:
- Invalid Typst snippets fail render/export with a
TypstError. .typst(true)is only available whentypst-mathis enabled at compile time.- Without
typst-math, the compiler reports:
error[E0599]: no method named `typst` found for struct `ruviz::core::Plot` in the current scope
- If you select the text engine directly,
TextEngineMode::Typstis also unavailable withouttypst-math, and the compiler reports:
error[E0599]: no variant or associated item named `Typst` found for enum `TextEngineMode` in the current scope
- If Typst is optional in your own crate, define and forward a local feature first:
[dependencies]
ruviz = { version = "0.4.9", default-features = false }
[features]
default = []
typst-math = ["ruviz/typst-math"]
- Then guard the call with your crate feature:
use ruviz::prelude::*;
let mut plot = Plot::new()
.line(&x, &y)
.title("$f(x) = e^(-x)$")
.xlabel("$x$")
.ylabel("$f(x)$");
#[cfg(feature = "typst-math")]
{
plot = plot.typst(true);
}
plot.save("typst_plot.png")?;
- Migration:
.latex(true)has been removed; use.typst(true)instead. - Typst text in PNG output is rasterized at native output scale (1x).
- For maximum text sharpness, prefer higher DPI (for example
.dpi(300)) or vector export (.export_svg(...)/.save_pdf(...)). - DPI changes output density, not the intended physical size of fonts, strokes, markers, or layout spacing.
- Prefer
.size(width_in, height_in)when you care about physical figure size..size_px(width, height)is a convenience that maps pixels through the 100-DPI reference size before final output DPI is applied. - Ticks are enabled by default and render inward on all four sides. Use
.ticks(false)to hide tick marks and tick labels while keeping the frame and axis titles. - Migration: if you want the older bottom/left-only tick appearance, call
.ticks_bottom_left(). - Migration: if you previously relied on high-DPI exports making lines, markers, or text look larger, set those sizes explicitly instead of relying on DPI.
Tick customization:
use ruviz::prelude::*;
Plot::new()
.line(&x, &y)
.tick_direction_inout()
.ticks_bottom_left()
.show_top_ticks(true)
.show_right_ticks(true)
.save("custom_ticks.png")?;
Examples
Basic Line Plot
use ruviz::prelude::*;
let x = vec![0.0, 1.0, 2.0, 3.0, 4.0];
let y = vec![0.0, 1.0, 4.0, 9.0, 16.0];
Plot::new()
.line(&x, &y)
.title("My First Plot")
.save("output.png")?;
Multi-Series with Styling
use ruviz::prelude::*;
let x = vec![0.0, 1.0, 2.0, 3.0, 4.0];
Plot::new()
.line(&x, &x.iter().map(|&x| x).collect::<Vec<_>>())
.label("Linear")
.line(&x, &x.iter().map(|&x| x * x).collect::<Vec<_>>())
.label("Quadratic")
.line(&x, &x.iter().map(|&x| x.powi(3)).collect::<Ve
