rustyplot
GPU-accelerated plotting for Python, with a Rust core.
import numpy as np
import rustyplot as rp
x = np.random.normal(size=1_000_000)
y = 0.6 * x + np.random.normal(size=1_000_000)
fig, ax = rp.subplots()
ax.scatter(x, y, size=2.0)
ax.xlabel = "x"
ax.title = "A million points"
fig
Renders in Jupyter and in a native desktop window from the same Rust code: the core is compiled to WebAssembly for the notebook, where it draws through WebGPU (falling back to WebGL2). Pan and zoom are handled inside the WebAssembly module, so navigating a figure never round-trips to the kernel.
Status: early prototype. Scatter and line plots, axes decorations and a pan/zoom toolbar. No images,
pcolormesh, legends or colorbars yet, and no 3D.
Installing
pip install rustyplot
The wheel ships the prebuilt WebAssembly bundle, so no Rust toolchain is needed to use it. To build from source instead, see Development.
Usage
The API follows matplotlib's object-oriented interface: a Figure owns a grid of
Axes, and artists are created by methods on an Axes. There is no global current
figure — no pyplot, no gca(). Displaying a figure means ending a cell with it.
Figures and axes
subplots() returns (fig, ax), with ax a single Axes for a 1x1 figure or a
row-major tuple otherwise:
fig, ax = rp.subplots(1, 2)
ax[0].scatter(x, y)
ax[1].plot(t, signal, line={"style": "solid"})
fig
For a one-liner, rp.scatter(x, y) and rp.plot(x, y) build a single-axes figure and
return it directly.
Properties instead of getters and setters
Where matplotlib has set_xlim()/get_xlim(), rustyplot has a property. Reads return
immutable values, and ax.xlim reflects what you are currently looking at, so it
follows along as you pan and zoom:
ax.title = "Measured response"
ax.xlabel = "time (s)"
ax.ylabel = "amplitude"
ax.xlim = (-1.0, 2.0)
ax.xlim # -> (-1.0, 2.0), a tuple
Lines
plot() needs at least one of line or marker, and each call adds a line rather
than replacing the previous one:
ax.plot(t, raw, line={"style": "dashed", "width": 1.0}, color="#999999")
ax.plot(t, fitted, line={"style": "solid", "width": 2.0}, marker={"style": "o", "size": 4})
scatter() differs: an axes holds a single scatter series, so a second call replaces it.
Mutable artist handles
Both scatter() and plot() return a handle that stays live. Assigning to it updates
the existing GPU buffers in place instead of rebuilding the plot:
pts = ax.scatter(x, y)
pts.y = np.sin(x) # redraws without re-sending x
pts.set(color="#ff0000", size=10) # several changes, one sync
Scatter handles expose x, y, xy, size and color; line handles expose x, y
and xy. Colours are #rrggbb/#rrggbbaa strings, an RGB(A) tuple, or a per-point
array; named colours are not supported yet.
Batching updates
Each assignment is normally one message to the browser. fig.hold() collapses a group
of them into a single sync and a single redraw:
with fig.hold():
ax.xlabel = "time"
ax.ylabel = "value"
ax.xlim = (0, 10)
Clicks
@fig.on_click
def report(event):
print(event["axes"], event["x"], event["y"], event.get("index"))
The event gives the axes clicked and the data coordinates under the cursor, plus the index of the nearest point when one was hit.
Layout
| Path | Contents |
|---|---|
crates/rustyplot-core |
Scene description, view maths, interaction. No GPU, no Python. |
crates/rustyplot-render |
wgpu renderer, WGSL shaders. |
crates/rustyplot-app |
Native window, for developing without a browser. |
crates/rustyplot-wasm |
WebAssembly bindings driven from JavaScript. |
python/rustyplot |
Python API and the anywidget frontend. |
rustyplot-core deliberately has no dependencies, so that a non-GPU backend (SVG output,
for instance) can be added later without disturbing it.
Development
Building from source needs the Rust toolchain, the wasm32-unknown-unknown target and
wasm-bindgen-cli. The CLI version must match the wasm-bindgen crate version pinned in
crates/rustyplot-wasm/Cargo.toml exactly:
rustup target add wasm32-unknown-unknown
cargo install wasm-bindgen-cli
Then build the WebAssembly bundle and install the package in editable mode:
./build.sh
pip install -e '.[test]'
build.sh writes the bundle into python/rustyplot/static/, which is git-ignored and
rebuilt rather than committed. Restart the Jupyter kernel after rebuilding, since the
bundle is cached in widget state.
cargo test --workspace # Rust
python3 -m pytest # Python and frontend syntax
cargo run -p rustyplot-app --release # native window, RUSTYPLOT_POINTS=5000000 to stress it
There is no JavaScript build step: the frontend is a single hand-written ES module, and
no npm packages are involved. node is used only to syntax-check that file during
tests, and is optional.
examples/demo.html renders the same plot outside Jupyter; serve the repository over
HTTP and open it to check the browser path on its own.
Licence
BSD-3-Clause.
Metadata
Release files for rustyplot 26.9.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| rustyplot-26.9.0.tar.gz | 1.7 MB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| rustyplot-26.9.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 3.4 MB
Release files / rustyplot-26.9.0.tar.gz
| Download URL | rustyplot-26.9.0.tar.gz |
|---|---|
| Size | 1.7 MB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
9eaab4f66df661100e6ca75b1cb4facb21133d499d737a9c661fef5291fc3a7b
|
|
BLAKE2b-256 checksum How to use checksums |
6547e301eaa3f7bd985a43edd6d85ecd6ac8e4e4114732d56033aa57ba4a21f6
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 15, 2026.
Transparency logRelease files / rustyplot-26.9.0-py3-none-any.whl
| Download URL | rustyplot-26.9.0-py3-none-any.whl |
|---|---|
| Size | 1.7 MB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
77ff04168489e9b3aff9979c597312d897be18f729f967a8c89c038961dbec9b
|
|
BLAKE2b-256 checksum How to use checksums |
0e2f8a15800ea5b4bdec4a216194e623e64dd07ed4747ac8c8665fdc0f18688b
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 15, 2026.
Transparency log