Binary data path
NumPy and typed arrays move as buffers. The Python and browser APIs avoid JSON point lists for series data.
Jupyter-native plotting, powered by ImPlot
nbimplot gives notebooks and browser apps the same interaction model: binary arrays into a strict ImGui + ImPlot WASM core, rendered on a canvas with pixel-bounded LOD for large time series.
drag pan / wheel zoom / double-click fitMinimal API, no global state
The public surface is intentionally small: create a plot, attach typed arrays, update handles in place, and let the WASM core manage interaction, state, LOD, subplots, and export.
pip install nbimplot
import nbimplot as ip
p = ip.Plot(width=900, height=450, title="Signal")
h = p.line("mid", df, x="time", y="mid")
h.set_data(df_next, x="time", y="mid")
p.show()npm install @nbimplot/web
import { createPlot } from "@nbimplot/web";
const plot = await createPlot(host, { title: "Signal" });
const h = plot.line("mid", y, { x: t });
h.setData(yNext, { x: tNext });
plot.render();Why this architecture
The Python layer validates and transfers buffers. The browser view owns the canvas lifecycle. The WASM core owns plot state, LOD, and ImPlot rendering.
NumPy and typed arrays move as buffers. The Python and browser APIs avoid JSON point lists for series data.
Pan, zoom, axis menus, legends, selection, and hover behavior come from the ImGui + ImPlot WASM runtime.
Large time series switch to min/max LOD so interaction cost tracks screen resolution, not raw array size.
Live API surface
These buttons load the target canvas, scroll it into view, and call the same public APIs available in notebooks and web apps.
Pick an action. The app will load the matching example and report the result here.
Interaction checklist
Examples lazy-load near the viewport and offscreen canvases are released to keep WebGL contexts bounded. Once loaded, left-drag pans, wheel zooms, right-click opens ImPlot menus, right-drag box-select/box-zoom follows ImPlot behavior, and double-click autofits.
Documentation
Direct resources for fast notebook plotting, million-point visualization, web app integration, and LLM/search positioning.
Examples gallery
Each canvas is an independent WASM session. Scroll to lazy-load examples and verify lifecycle cleanup, interactions, subplots, colormaps, and exports. Every card shows the equivalent Python notebook API and direct web API.
Performance
Explicit x/y buffers, WASM min/max LOD, and callback-driven hover/click/selection inspection.
p = ip.Plot(width=1000, height=420, title="Line + LOD")
h = p.line("signal", df, x="time", y="signal")
p.on_hover(lambda plot, e: print(e["index"], e["x"], e["y"]))
p.on_select(lambda plot, e: plot.indices_for_selection(e, h))
pconst h = plot.line("signal", y, { x });
plot.onHover(console.log);
plot.onSelection((e) => plot.indicesForSelection(e, h));Performance
Append explicit x/y chunks into a fixed-capacity line without recreating the plot object.
p = ip.Plot(width=1000, height=360, title="Realtime")
h = p.stream_line("ticks", capacity=12000, initial=y0, initial_x=x0, auto_render=True)
h.append(chunk, x=chunk_x)
h.pause(); h.resume()
pconst h = plot.streamLine("ticks", { capacity: 12000, x: initialX });
h.append(chunk, { x: chunkX });
h.pause(); h.resume();Data Input
Multi-series upload, automatic time/category x normalization, C++ theme presets, and standalone HTML state export.
p = ip.Plot(width=1100, height=420, title="Batch + Axes")
p.set_theme("publication")
handles = p.lines({"mid": {"x": ts, "y": mid}, "vwap": {"x": ts, "y": vwap}})
p.scatter("scores", scores, x=["A", "B", "C"])
html = p.export_html(title="snapshot")
pplot.setTheme("publication");
const handles = plot.lines({ mid: { x: dates, y: mid }, vwap: { x: dates, y: vwap } });
plot.scatter("scores", scores, { x: ["A", "B", "C"] });
const html = plot.exportHTML({ title: "snapshot" });Points
Point-cloud rendering with explicit x/y data and bubble-size encodings for dense browser workflows.
p = ip.Plot(width=1000, height=420, title="Scatter + Bubbles")
p.scatter("samples", df, x="x", y="y", size=2.5)
p.bubbles("volume", df, x="x", y="y", sizes="volume")
pplot.scatter("samples", y, { x });
plot.bubbles("volume", y, sizes, { x });Curves
Signal-analysis overlays in one canvas: stepped series, impulses, bands, digital states, and uncertainty.
p = ip.Plot(width=1100, height=420, title="Signal Overlays")
p.stairs("step", y, x=x)
p.stems("stem", impulses, x=x)
p.digital("state", states, x=x)
p.shaded("band", lower, upper, x=x)
p.error_bars("fit", fit, err=err, x=x)
pplot.stairs("step", y, { x });
plot.shaded("band", lower, upper, { x });
plot.errorBars("fit", y, { x, err });Categorical
Vertical bars, grouped categories, and horizontal rankings across ImPlot subplots.
sp = ip.Subplots(1, 3, width=1100, height=360, title="Bars")
sp.subplot(0, 0).bars("sales", values)
sp.subplot(0, 1).bar_groups(["A", "B", "C"], matrix)
sp.subplot(0, 2).bars_h("rank", values)
spplot.setSubplots(1, 3);
plot.bars("sales", values);
plot.barGroups(labels, matrix);
plot.barsH("rank", values);Statistics
1D and 2D distributions with colorbar-backed density inspection.
p = ip.Plot(width=1100, height=420, title="Distributions")
p.histogram("returns", df, y="returns", bins=80)
p.histogram2d("density", df, x="x", y="y", x_bins=80, y_bins=60, show_colorbar=True)
pplot.histogram("returns", values, { bins: 80 });
plot.histogram2d("density", x, y, { xBins: 80, yBins: 60 });Matrices
Matrix and image plotting with empty heatmap labels, colorbar formatting, and float RGB buffers.
p = ip.Plot(width=1100, height=420, title="Heatmap + Image")
p.set_colormap("Viridis")
p.heatmap("z", matrix, label_fmt="", show_colorbar=True, colorbar_format="%.2f")
p.image("rgb", image, bounds=((0, 0), (cols, rows)))
pplot.setColormap("Viridis");
plot.heatmap("z", matrix, { rows, cols, labelFmt: "" });
plot.image("rgb", image, { rows, cols, channels: 3 });Overlays
Thresholds, callouts, labels, tags, and pie-chart composition using ImPlot primitives.
p = ip.Plot(width=1100, height=420, title="Overlays")
p.vlines("events", xs)
p.hlines("limits", ys)
p.tag_y(0.0, label_fmt="zero")
p.annotation("peak", x0, y0)
p.pie_chart("mix", values, labels=labels, x=8, y=0, radius=1)
pplot.vlines("events", xs);
plot.tagY(0, { labelFmt: "zero" });
plot.annotation("peak", x, y);
plot.pieChart("mix", values, { labels });Axes
Secondary axes, custom ticks, numeric formats, linked axes, and log/time scale controls.
p = ip.Plot(width=1100, height=420, title="Axes")
p.set_secondary_axes(y2=True)
p.set_axis_scale(x="linear", y="log")
p.set_axis_label("x1", "time")
p.set_axis_format("y1", "%.2e")
p.set_axis_ticks("x1", ticks, labels=labels)
p.line("primary", y, x=x)
p.line("secondary", y2, x=x, y_axis="y2")
pplot.setSecondaryAxes({ y2: true });
plot.setAxisScale({ x: "linear", y: "log" });
plot.setAxisTicks("x1", ticks, { labels });Layout
A 2x2 ImPlot subplot grid with linked x-axis behavior and crosshair synchronization.
sp = ip.Subplots(2, 2, link_all_x=True, width=1100, height=650, title="Linked")
sp.set_linked_crosshair("desk", axis="x")
sp.subplot(0, 0).line("sin", y0, x=x)
sp.subplot(0, 1).line("cos", y1, x=x)
sp.subplot(1, 0).scatter("noise", y2, x=x)
spplot.setSubplots(2, 2, { linkAllX: true });
plot.setLinkedCrosshair("desk", { axis: "x" });
plot.line("a", y, { x, subplotIndex: 0 });Interaction
Interactive ImPlot primitives for draggable guides, anchors, rectangles, and drop targets.
p = ip.Plot(width=1100, height=420, title="Drag Tools")
p.drag_line_x("cursor", 40)
p.drag_line_y("threshold", 0.5)
p.drag_point("anchor", 25, 0.5)
p.drag_rect("roi", 10, -1, 20, 1)
p.on_tool_change(lambda plot, event: print(event))
pplot.dragLineX("cursor", 40);
plot.dragPoint("anchor", 25, 0.5);
plot.onInteraction(events => ...);Interaction events: move a drag primitive.
Colormaps
Selector, slider, and color button widgets that update heatmaps and colorbar primitives at runtime.
p = ip.Plot(width=1000, height=420, title="Colormaps")
p.set_colormap("Plasma")
p.heatmap("z", matrix, label_fmt="", show_colorbar=True)
p.colormap_selector(label="Choose map")
p.colormap_slider(label="Sample")
p.colormap_button(label="Active")
pplot.setColormap("Plasma");
plot.colormapSelector({ label: "Choose map" });
plot.colormapSlider({ label: "Sample" });Specialty
Financial primitives rendered in the ImPlot/WASM layer with hover inspection and autoscale support.
p = ip.Plot(width=1100, height=420, title="Finance")
p.set_theme("finance")
p.candlestick("candles", x=x, open=open_, high=high, low=low, close=close)
p.ohlc("ohlc", x=x, open=open_, high=high, low=low, close=close)
pplot.setTheme("finance");
plot.candlestick("candles", open, high, low, close, { x });
plot.ohlc("ohlc", open, high, low, close, { x });Specialty
Scientific field and matrix workflows backed by WASM-side ImPlot primitives and draw-list integration.
p = ip.Plot(width=1100, height=760, title="Scientific")
p.set_subplots_config(rows=2, cols=2)
p.contour("contour", z, levels=levels, subplot_index=0)
p.quiver("field", x, y, u, v, normalize=True, subplot_index=1)
p.waterfall("waterfall", z, subplot_index=2)
p.spectrogram("spectrogram", z, label_fmt="", show_colorbar=True, subplot_index=3)
pplot.setSubplots(2, 2);
plot.contour("contour", z, { rows, cols, levels, subplotIndex: 0 });
plot.quiver("field", x, y, u, v, { normalize: true, subplotIndex: 1 });
plot.waterfall("waterfall", z, { rows, cols, subplotIndex: 2 });
plot.spectrogram("spectrogram", z, { rows, cols, labelFmt: "", showColorbar: true, subplotIndex: 3 });Advanced API
View callbacks, state snapshots, PNG export, selection CSV, highlighting, constraints, links, and direct primitive access.
p = ip.Plot(width=1100, height=420, title="Advanced")
p.set_theme("nbimplot")
h = p.line("signal", y, x=x)
state = p.get_state(include_data=True)
p.highlight_selection(selection, h)
csv = p.export_csv_selection(selection, h)
p.export_png("advanced.png")
pplot.setTheme("nbimplot");
const state = plot.getState({ includeData: true });
plot.highlightSelection(selection, h);
const csv = plot.exportCSVSelection(selection, h);
await plot.downloadPNG("advanced.png");