Skip to content

Ameyanagi/ruviz

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

438 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

ruviz

High-performance 2D plotting library for Rust combining matplotlib's ease-of-use with Makie's performance.

Crates.io Documentation License CI

Release Notes

Package Surfaces

  • Rust crate: ruviz
  • GPUI adapter crate: ruviz-gpui
  • Raw Rust wasm bridge: ruviz-web
  • Python package: ruviz in python/
  • npm package: ruviz in packages/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.

Example Plot

Features

🛡️ Safety & Quality

  • Zero unsafe in public API
  • Strong type system prevents runtime errors
  • Comprehensive error handling with Result types
  • 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 pdf feature)
  • 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-web adapter and ruviz npm SDK for wasm32 canvas 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.12"

Feature Flags

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.

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:

  • ruviz automatically 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, and bun run test:web from 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.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 when typst-math is 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::Typst is also unavailable without typst-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")?;

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::<Vec<_>>())
    .label("Cubic")
    .title("Polynomial Functions")
    .xlabel("x")
    .ylabel("y")
    .theme(Theme::publication())
    .save("polynomials.png")?;

Grouped Series with Shared Styling

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")?;

Subplots

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")?;

Large Dataset

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")?;

Animation

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)
    }
)?;

Animation Example

Interactive And Animation Example Catalog

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_performance

Default interactive window controls:

  • Mouse wheel: zoom in/out under the cursor
  • Left click + drag: pan
  • Right click: open the context menu
  • Right click + drag: box zoom
  • Escape: close the menu or reset the view
  • Cmd/Ctrl+S: save the current view as PNG
  • Cmd/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_gallery

Use 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.

Typst Text Example

Run:

cargo run --example doc_typst_text --features typst-math

Documentation

Why ruviz?

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")?;

Contributing

Contributions welcome! See CONTRIBUTING.md for setup, testing, and pull request guidelines.

Development

# 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-features

The 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.

Roadmap

  • 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+)

License

Licensed under either of:

at your option.

Acknowledgments


Status: v0.4.12 - Early development, API may change. Production use at your own risk.

Support: Open an issue or start a discussion

About

High-performance 2D plotting library for Rust combining matplotlib's ease-of-use with Makie's performance.

Resources

License

Contributing

Stars

Watchers

Forks

Packages

 
 
 

Contributors