Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

1,148 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

pluot

pluots

blog post  ✦  docs  ✦  example  ✦  crate  ✦  pypi  ✦  npm  ✦  paper

Implement a data visualization once, then render it in multiple contexts (across languages, static or interactive, bitmap or vector).

Rust, Python, R, and JavaScript (including in a web browser) are currently supported.

🧪 Pluot is new and experimental.

Features

  • Small: The bundle size (i.e., the WASM binary size) is small (currently less than 5MB) to make it feasible to integrate into web applications.
  • Scalable: Scales to out-of-memory dataset sizes using partial reads of arrays/columns and data tiling/aggregation strategies (currently using Zarr via zarrs to achieve this).
  • Language bindings: Usable from multiple languages, including JavaScript/TypeScript (via WASM), Python (via PyO3/maturin bindings), and R (via extendr bindings).
  • Bitmap or Vector Outputs: Plotting functions can implement bitmap and vector equivalent drawing logic, to support publication-quality graphics export.
  • Layer-based API: Compose the built-in layers to create complex plots, or build your own layers with full control over the WebGPU shaders, buffers, and draw calls. Usage of WebGPU compute (GPGPU) operations prior to each layer's draw call is also supported (regardless of whether bitmap or vector output format).

⚠️ Currently focusing on 2D, before eventually supporting polar, ternary, and 3D.

How it works

Pluot uses Rust 🦀 and WebGPU via wgpu to quickly render plots to an array of pixels (or an SVG string), decoupled from any windowing system or interpreted language runtime. On each "frame" of an interaction or animation, we re-render with updated plotting parameters.

When the language bindings are used, you can think of this as a form of "remote rendering", which is actually happening locally; rather than the "remote" being a far-away server, it is just across the language binding boundary.

Why would I want to use this? Why not just use JS+WebGPU directly?

The main reasons are:

  • Portability: render plots from multiple languages, without the overhead of an interpreted programming language runtime
  • Reproducibility: explore data using an interactive tool (e.g., web or desktop GUI) to identify plotting parameters of interest, and then use the same parameter values in a scripting language to reproduce the visualization as a static plot (e.g., a Python script in a Snakemake pipeline)

Using WebGPU via JavaScript would couple things to JavaScript, which we do not want for a library that should be usable in multiple languages, including without a JS runtime. Our approach enables our CPU-based operations to benefit from the performance characteristics of Rust (or, in web contexts, at least those of Rust-via-WASM).

You can likely achieve better performance by using WebGPU directly via JavaScript. The question is whether the performance of this Rust-based approach is good enough, and whether the benefits are worth the potential performance tradeoffs for your use case.

Development

Further developer documentation, including troubleshooting tips, can be found in dev-docs.

Set up environment

After cloning the repository, pull down the submodule containing font files.

git submodule update --init --recursive
cd bindings-js/core && pnpm run copy-fonts && cd -

Install Rust tools with Rustup.

# Install rustup
cargo install wasm-pack
cargo install cargo-edit
cargo build

# Install pnpm
# may need to run `wasm-pack build crates/pluot --target web` first
pnpm install

# Install uv

# Generate/download sample data
# See data/README.md

uv sync --extra dev

Build for WASM

# Install nightly version of wasm-bindgen CLI (potentially not needed anymore)
# Reference: https://github.com/wasm-bindgen/wasm-bindgen/issues/4446#issuecomment-3172624621
cargo install --git https://github.com/rustwasm/wasm-bindgen --rev b766ac3e206a8efab2c7cf91923cd502b2bc77a5 wasm-bindgen-cli

wasm-pack build crates/pluot --target web && pnpm run start-demo
# or
wasm-pack build crates/pluot --dev --target web && pnpm run start-demo
# or
wasm-pack build crates/pluot --release --target web && pnpm run start-demo

Test in Headless Browsers with wasm-pack test

wasm-pack test --headless --chrome crates/pluot
# or
wasm-pack test --headless --chrome crates/pluot -- --nocapture
# or
wasm-pack test --chrome crates/pluot
# or
wasm-pack test --firefox crates/pluot

Build for Python

uv sync --extra dev --extra widget

Build:

uv run maturin develop --features python

Run tests:

uv run pytest

Use in REPL:

uv run python -m asyncio
>>> from pluot import render_py
>>> await render_py(width=100, height=100, plotId="test", plotType="triangle", storeName="test")

Try in Marimo notebook:

uv run marimo edit

Try in Jupyter notebook:

uv run jupyter lab --notebook-dir bindings-python/notebooks

Build for plain Rust

cargo build

Run tests

cargo test
# or
cargo test --features lacks_gpu
# or, run a specific test file
cargo test -p pluot_core --test test_positioning

Lint with clippy

cargo clippy
cargo clippy --fix

Generate crate docs locally

cargo doc --no-deps
open target/doc/pluot/index.html

Build for R

With R and RStudio installed:

open bindings-r/pluotr.Rproj
devtools::install()

devtools::load_all()
# and/or
devtools::test()

Or, entirely via the command-line:

R CMD build bindings-r --no-build-vignettes
R CMD check pluotr_1.2.3.tar.gz --no-vignettes --no-build-vignettes --ignore-vignettes --no-manual

Inspired by

This work has been informed by my experiences in contributing to projects including vitessce, use-coordination, viv, cistrome-explorer, deck-to-svg, higlass, vueplotlib, and easy_vitessce.

It is also inspired by many other projects such as deck.gl, deck.gl-native, jupyter-scatter, gosling, napari-spatialdata, spatialdata-plot, and scanpy.

Related work

See awesome-rust-vis for a list of crates related to data visualization and plotting.

About the name

A pluot is a plum-apricot hybrid. The fruit's pit is to its flesh as the Rust core of this project is to its non-Rust bindings.

Rust learning resources

Citation

If you found this useful, please cite our preprint:

@article{keller2026pluot,
  title = {{Pluot: Towards 'write once, run everywhere' visualization software}},
  author = {Keller, Mark S. and Gehlenborg, Nils},
  journal = {arXiv},
  year = {2026},
  doi = {10.48550/arXiv.2605.14118}
}

License

See LICENSE and NOTICE.

About

Plotting, unified.

Topics

Resources

Stars

26 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages