ImPlot-powered Jupyter plotting widget for interactive, million-point visualizations.
Project description
nbimplot
Jupyter-native, ImPlot-powered plotting for very large arrays.
nbimplot is built around three constraints:
- notebook cell rendering only (no native windows, no side process)
- binary transfer (
numpy -> bytes -> wasm heap) - WASM-owned interaction, state, and LOD
The runtime is strict: ImPlot + WASM + WebGL2 are required.
Project note: nbimplot was vibe coded with Codex, then
hardened with tests, packaging checks, and runtime validation.
Install
python -m pip install -U nbimplot
Minimum recommended widget/runtime stack:
python -m pip install -U "nbimplot>=0.1.8" "anywidget>=0.9.21" ipywidgets jupyterlab_widgets
Compatibility
- Python
>=3.10 - Jupyter widget stack:
anywidget,ipywidgets,traitlets - Frontend: JupyterLab/Notebook with widget manager enabled
- Browser/GPU: WebGL2 required
- Runtime mode: strict WASM + ImPlot only (no JS renderer fallback)
Quick Start
import numpy as np
import nbimplot as ip
y = np.sin(np.linspace(0, 100, 1_000_000, dtype=np.float32))
p = ip.Plot(width=900, height=450, title="Signal")
h = p.line("mid", y)
p.show()
# Update in place, then redraw
h.set_data((0.8 * y).astype(np.float32))
p.render()
Interaction Defaults
- initial X/Y view auto-fits to available data
- double-click inside plot area resets view (
autoscale) - right-drag box zoom, wheel zoom, drag pan, legend toggle
Core API
import numpy as np
import nbimplot as ip
y = np.random.randn(200_000).astype(np.float32).cumsum()
p = ip.Plot(width=1000, height=420, title="Core API")
h = p.line("price", y, color="#22c55e", line_weight=2.0, marker="none")
p.set_plot_flags(no_legend=False, no_menus=False, no_box_select=False)
p.set_colormap("Viridis")
p.show()
# Later update
h.set_data((y * 1.01).astype(np.float32))
p.render()
Common Examples
1) Line + Streaming
import numpy as np
import nbimplot as ip
p = ip.Plot(width=1000, height=380, title="Streaming")
h = p.stream_line("ticks", capacity=200_000, initial=np.zeros(1000, dtype=np.float32))
p.show()
chunk = np.random.randn(20_000).astype(np.float32)
h.append(chunk)
p.render()
2) Scatter / Bars / Histogram
import numpy as np
import nbimplot as ip
rng = np.random.default_rng(7)
x = rng.normal(0, 1, 4000).astype(np.float32)
y = (0.5 * x + 0.2 * rng.normal(size=x.size)).astype(np.float32)
p = ip.Plot(width=1100, height=420, title="Stat Plots")
p.scatter("cloud", y, x=x, size=2.0)
p.vlines("cuts", np.array([-1.0, 1.0], dtype=np.float32))
p.hlines("zero", np.array([0.0], dtype=np.float32))
p.show()
p2 = ip.Plot(width=1100, height=360, title="Histogram")
p2.histogram("x-dist", x, bins=60)
p2.show()
3) Heatmap / Histogram2D / Image
import numpy as np
import nbimplot as ip
rng = np.random.default_rng(0)
z = rng.normal(size=(50, 80)).astype(np.float32)
p = ip.Plot(width=1100, height=420, title="Heatmap")
p.set_colormap("Plasma")
p.heatmap(
"z",
z,
label_fmt="", # empty format disables cell text
show_colorbar=True,
colorbar_label="Intensity",
colorbar_format="%.3f",
)
p.show()
x = rng.normal(size=200_000).astype(np.float32)
y = (0.3 * x + rng.normal(size=x.size)).astype(np.float32)
p2 = ip.Plot(width=1100, height=420, title="Histogram2D")
p2.histogram2d(
"h2d",
x,
y,
x_bins=100,
y_bins=80,
label_fmt="",
show_colorbar=True,
colorbar_label="Count",
)
p2.show()
4) Subplots
import numpy as np
import nbimplot as ip
sp = ip.Subplots(
2,
2,
title="Dashboard",
width=1100,
height=760,
link_rows=True,
link_cols=True,
share_items=True,
)
t = np.linspace(0, 30, 4000, dtype=np.float32)
sp.subplot(0, 0).line("sin", np.sin(t))
sp.subplot(0, 1).scatter("noise", np.random.randn(3000).astype(np.float32))
sp.subplot(1, 0).bars("bars", np.abs(np.random.randn(120)).astype(np.float32))
sp.subplot(1, 1).histogram("hist", np.random.randn(20_000).astype(np.float32), bins=50)
sp.show()
Plot and Primitive Coverage
Implemented plot/primitive APIs include:
line,stream_linescatter,bubbles,stairs,stems,digitalbars,bar_groups,bars_h,shadederror_bars,error_bars_hinf_lines,vlines,hlineshistogram,histogram2d,heatmap,image,pie_charttext,annotation,dummytag_x,tag_y,colormap_slider,colormap_button,colormap_selectordrag_line_x,drag_line_y,drag_point,drag_rectdrag_drop_plot,drag_drop_axis,drag_drop_legend
For a broader cookbook, see docs/EXAMPLES.md.
View, Axes, and Performance Controls
p.set_view(x_min, x_max, y_min, y_max)p.autoscale()p.set_axis_scale(x="linear|log", y="linear|log")p.set_axis_state("x2|x3|y2|y3", enabled=True|False, scale="linear|log|time")p.set_secondary_axes(x2=..., x3=..., y2=..., y3=...)p.set_time_axis("x1|x2|x3|y1|y2|y3")p.set_axis_label(...),p.set_axis_format(...)p.set_axis_ticks(...),p.clear_axis_ticks(...)p.set_axis_limits_constraints(...),p.set_axis_zoom_constraints(...),p.set_axis_link(...)p.hide_next_item()p.on_perf_stats(callback, interval_ms=500)p.on_tool_change(callback)p.on_selection_change(callback)
Example Notebooks
notebooks/nbimplot_examples.ipynbnotebooks/nbimplot_api_gallery.ipynbnotebooks/nbimplot_benchmarks.ipynb
Troubleshooting
Failed to load model class 'AnyModel' from module 'anywidget'
This is usually a server-kernel env mismatch or stale lab assets.
python -m pip install -U "nbimplot>=0.1.8" "anywidget>=0.9.21" ipywidgets jupyterlab_widgets
jupyter lab clean
Then restart the full JupyterLab server.
Quick verification:
import nbimplot as ip, anywidget, sys
print("python:", sys.executable)
print("anywidget:", anywidget.__version__)
print("has Plot:", hasattr(ip, "Plot"))
Unable to enable ImPlot in the WASM core or WebGL context errors
Strict mode requires WebGL2.
!!document.createElement("canvas").getContext("webgl2")
If this is false, run from a local desktop browser session with GPU
acceleration enabled.
Known Limitations
- Rendering requires WebGL2-capable browser/runtime.
- Strict mode is enforced; non-ImPlot or non-WASM fallback is disabled.
- Headless/browser-restricted environments may fail to create GL context.
Build WASM Core
Prerequisites:
- Emscripten SDK (
emcmake,emcc) - CMake >= 3.20
Build:
scripts/build_wasm.sh
Expected outputs in nbimplot/wasm/:
nbimplot_wasm.jsnbimplot_wasm.wasm
Build explicitly with local ImGui/ImPlot sources:
NBIMPLOT_WITH_IMPLOT=ON \
NBIMPLOT_IMGUI_DIR=/path/to/imgui \
NBIMPLOT_IMPLOT_DIR=/path/to/implot \
scripts/build_wasm.sh
If you use vendored deps:
NBIMPLOT_WITH_IMPLOT=ON scripts/build_wasm.sh
Performance Model
- raw path when visible points
<= 3 * pixel_width - min/max LOD when visible points
> 3 * pixel_width - LOD computed in WASM, complexity scales with screen pixels
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file nbimplot-0.1.8.tar.gz.
File metadata
- Download URL: nbimplot-0.1.8.tar.gz
- Upload date:
- Size: 393.1 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.10.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
8b922d9d537b14a730588ee4e8d71024967eaa98f89e3a5a5ca0cab8495572f5
|
|
| MD5 |
a8274ba13e57a723e43c009fcfd76ffb
|
|
| BLAKE2b-256 |
1220a41862bd219470664b4987ad442d6530ccef5de4707b5cdba981a93f505e
|
File details
Details for the file nbimplot-0.1.8-py3-none-any.whl.
File metadata
- Download URL: nbimplot-0.1.8-py3-none-any.whl
- Upload date:
- Size: 393.7 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.10.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
c5b0b1c7fafd0dd8af300be212a18bfa1a5e7961912fbecb820bce039b62c592
|
|
| MD5 |
29cb550dec8b153133f7fdcdce2e24e3
|
|
| BLAKE2b-256 |
60b4382b41be09d8133c6ac3da5aae50a65d315fafda96734f91a843d37de6ab
|