High-performance 2D plotting library for Rust combining matplotlib's ease-of-use with Makie's performance.
- 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.
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.
- Zero unsafe in public API
- Strong type system prevents runtime errors
- Comprehensive error handling with
Resulttypes - Memory-safe by design
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)
- 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
- 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
Add to your Cargo.toml:
[dependencies]
ruviz = "0.4.12"Choose features based on your needs:
[dependencies]
ruviz = { version = "0.4.12", 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.
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.
The repo now includes a mixed Python package in python built with uv, maturin,
and pyO3.
cd python
uv sync
uv run maturin developThe 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.
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.
Enable Typst text rendering:
[dependencies]
ruviz = { version = "0.4.12", 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.12", 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")?;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")?;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::<Vec<_>>())
.label("Cubic")
.title("Polynomial Functions")
.xlabel("x")
.ylabel("y")
.theme(Theme::publication())
.save("polynomials.png")?;use ruviz::prelude::*;
let x = vec![0.0, 1.0, 2.0, 3.0];
let a = vec![0.0, 1.0, 2.0, 3.0];
let b = vec![0.0, 1.5, 3.0, 4.5];
let baseline = vec![0.2, 1.2, 2.2, 3.2];
Plot::new()
.group(|g| {
g.group_label("Sensors")
.line_style(LineStyle::Dashed)
.line_width(2.0)
.color(Color::from_hex("#1E88E5").unwrap())
.line(&x, &a)
.line(&x, &b)
})
.line(&x, &baseline)
.label("Baseline")
.legend(Position::TopRight)
.save("grouped_series.png")?;use ruviz::prelude::*;
let plot1 = Plot::new().line(&x, &y).title("Line").end_series();
let plot2 = Plot::new().scatter(&x, &y).title("Scatter").end_series();
let plot3 = Plot::new().bar(&["A", "B", "C"], &[1.0, 2.0, 3.0]).title("Bar").end_series();
let data = vec![0.5, 1.0, 1.5, 2.0, 2.5];
let plot4 = Plot::new()
.histogram(&data, None)
.title("Histogram")
.end_series();
subplots(2, 2, 800, 600)?
.suptitle("Scientific Analysis")
.subplot(0, 0, plot1)?
.subplot(0, 1, plot2)?
.subplot(1, 0, plot3)?
.subplot(1, 1, plot4)?
.save("subplots.png")?;use ruviz::prelude::*;
// 100,000-point PNG export
let x: Vec<f64> = (0..100_000).map(|i| i as f64).collect();
let y: Vec<f64> = x.iter().map(|&x| x.sin()).collect();
Plot::new()
.line(&x, &y)
.title("Large Dataset")
.save("large.png")?;Enable the animation feature for this example:
[dependencies]
ruviz = { version = "0.4.12", features = ["animation"] }use ruviz::prelude::*;
use ruviz::animation::RecordConfig;
use ruviz::record;
let x: Vec<f64> = (0..100).map(|i| i as f64 * 0.1).collect();
let config = RecordConfig::new().max_resolution(800, 600).framerate(30);
record!(
"wave.gif",
2 secs,
config: config,
|t| {
let phase = t.time * 2.0 * std::f64::consts::PI;
let y: Vec<f64> = x.iter().map(|&xi| (xi + phase).sin()).collect();
Plot::new()
.line(&x, &y)
.title(format!("Wave Animation (t={:.2}s)", t.time))
.xlim(0.0, 10.0)
.ylim(-1.5, 1.5)
}
)?;Interactive window examples:
cargo run --features interactive --example basic_interaction
cargo run --features interactive --example interactive_multi_series
cargo run --features interactive --example interactive_scatter_clusters
cargo run --features interactive --example interactive_heatmap
cargo run --features interactive --example data_brushing
cargo run --features interactive --example real_time_performanceDefault interactive window controls:
Mouse wheel: zoom in/out under the cursorLeft click + drag: panRight click: open the context menuRight click + drag: box zoomEscape: close the menu or reset the viewCmd/Ctrl+S: save the current view as PNGCmd/Ctrl+C: copy the current view as an image
The standalone interactive window and the ruviz-gpui adapter are supported on
Linux, macOS, and Windows. On Windows, the recommended CI/native target is
x86_64-pc-windows-msvc.
The built-in context menu includes Reset View, Set Current View As Home,
Go To Home View, Save PNG..., Copy Image, Copy Cursor Coordinates, and
Copy Visible Bounds. You can extend it with custom items through
InteractiveWindowBuilder::context_menu(...) and
InteractiveWindowBuilder::on_context_menu_action(...).
Animation export examples:
cargo run --features animation --example animation_basic
cargo run --features animation --example animation_simple
cargo run --features animation --example animation_wave
cargo run --features animation --example animation_easing
cargo run --features animation --example animation_reactive
cargo run --features animation --example generate_animation_galleryUse the interactive examples when you want zoom/pan exploration in a window. Use the
animation examples when you want rendered GIF output with the record! macro and easing
helpers.
Run:
cargo run --example doc_typst_text --features typst-math- User Guide - Comprehensive tutorials and examples
- API Documentation - Complete API reference
- Gallery - Visual examples showcase
- Migration from matplotlib - For Python users
- Migration from seaborn - Statistical plots
- Performance Guide - Optimization techniques
- Large Dataset Benchmarks - Cross-runtime PNG rendering results for Rust, Python, wasm, and matplotlib
- Rust Feature Impact Benchmarks - Rust-only feature-flag study for render and save backend performance
Rust's plotting ecosystem has several options, but each has trade-offs:
| Library | Approach | Limitation |
|---|---|---|
| plotters | Low-level drawing API | Verbose, requires boilerplate for common plots |
| plotly.rs | JavaScript bindings | Requires JS runtime, web-focused |
| plotpy | Python/matplotlib wrapper | Requires Python installed |
ruviz fills the gap with:
- High-level API: matplotlib-style
Plot::new().line().title().save()- no boilerplate - Pure Rust: No Python, JavaScript, or external runtime needed
- Built-in plot types: 15+ plot types out of the box (violin, KDE, radar, etc.)
- Publication quality: Professional themes and high-DPI export
// plotters: ~30 lines for a simple line plot
// ruviz: 4 lines
Plot::new()
.line(&x, &y)
.title("My Plot")
.save("plot.png")?;Contributions welcome! See CONTRIBUTING.md for setup, testing, and pull request guidelines.
# Clone repository
git clone https://github.com/Ameyanagi/ruviz.git
cd ruviz
bun install
# bun install auto-installs the repo's Lefthook hooks for local clones.
# Re-run manually if install scripts are disabled or you need to migrate an older clone.
make setup-hooks
# Run code quality checks
make check
# Run tests
cargo test --all-features
# Run examples
cargo run --example basic_example --release
# Run benchmarks
cargo bench --all-featuresThe repo uses Lefthook for local git hooks. bun install installs the hooks automatically for local clones, and make setup-hooks reinstalls them when install scripts are disabled or an older checkout still has the legacy .githooks setup. The pre-commit hook runs cargo fmt --check, cargo clippy, oxfmt --check, and oxlint --deny-warnings. To debug the hook manually, run bunx lefthook run pre-commit.
- Core plot types (line, scatter, bar, histogram, boxplot, heatmap)
- Parallel rendering
- SIMD optimization
- GPU acceleration (experimental)
- Professional themes
- Subplots and multi-panel figures
- Distribution plots: Violin, KDE, ECDF
- Composition plots: Pie, Donut
- Continuous plots: Contour
- Polar plots: Polar, Radar
- Error bars
- SVG export
- Experimental interactive window support
- High-level APIs for area, hexbin, step, and stem plots
- High-level APIs for regression and composite plots
- Stabilize the interactive zoom/pan workflow
- 3D plotting (v1.0+)
Licensed under either of:
- Apache License, Version 2.0 (LICENSE or http://www.apache.org/licenses/LICENSE-2.0)
- MIT license (LICENSE or http://opensource.org/licenses/MIT)
at your option.
- Inspired by matplotlib, seaborn, and Makie.jl
- Built with tiny-skia for rendering
- Text rendering by cosmic-text
- Thanks to the Rust community for excellent crates and feedback
Status: v0.4.12 - Early development, API may change. Production use at your own risk.
Support: Open an issue or start a discussion

