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.
nbimplot is built on top of these upstream projects:
- Dear ImGui: https://github.com/ocornut/imgui
- ImPlot: https://github.com/epezent/implot
Special thanks to the ImPlot and Dear ImGui maintainers and contributors for
building and maintaining the core libraries that make nbimplot possible.
python -m pip install -U nbimplotMinimum recommended widget/runtime stack:
python -m pip install -U "nbimplot>=0.1.8" "anywidget>=0.9.21" ipywidgets jupyterlab_widgets- 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)
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()- 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
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()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()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()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()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()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.
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)
notebooks/nbimplot_examples.ipynbnotebooks/nbimplot_api_gallery.ipynbnotebooks/nbimplot_benchmarks.ipynbnotebooks/nbimplot_colab_complete.ipynb(Colab-ready complete examples)
Open the Colab notebook directly:
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 cleanThen 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"))Strict mode requires WebGL2.
!!document.createElement("canvas").getContext("webgl2")If this is false, run from a local desktop browser session with GPU
acceleration enabled.
- 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.
Prerequisites:
- Emscripten SDK (
emcmake,emcc) - CMake >= 3.20
- Dear ImGui sources (
imgui): https://github.com/ocornut/imgui - ImPlot sources (
implot): https://github.com/epezent/implot
Build:
scripts/build_wasm.shExpected 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.shIf you use vendored deps:
NBIMPLOT_WITH_IMPLOT=ON scripts/build_wasm.sh- 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