Metadata-Version: 2.4
Name: nbimplot
Version: 0.1.3
Summary: ImPlot-powered Jupyter plotting widget for interactive, million-point visualizations.
Author: nbimplot contributors
License: MIT
Keywords: implot,jupyter,plotting,visualization,widgets
Classifier: Framework :: Jupyter
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3 :: Only
Requires-Python: >=3.10
Requires-Dist: anywidget>=0.9.21
Requires-Dist: ipywidgets>=8.1.0
Requires-Dist: numpy>=1.24
Requires-Dist: traitlets>=5.14
Provides-Extra: dev
Requires-Dist: pytest>=8.0; extra == 'dev'
Description-Content-Type: text/markdown

# nbimplot

Jupyter-native plotting focused on high interactivity and large time-series support.

Current implementation includes:

- Notebook-only widget canvas rendering
- Binary transport (`numpy -> bytes -> wasm memory`)
- WASM core for plot state, autoscale, and min/max LOD generation
- Strict WASM runtime requirement (JS fallback disabled)
- ImPlot-required mode (`prefer_implot=True`, `strict_wasm=True` only)
- Lifecycle-safe cleanup for repeated cell execution
- Subplots via `nbimplot.Subplots`
- Expanded primitive APIs: `scatter`, `bubbles`, `stairs`, `stems`, `digital`,
  `bars`, `bar_groups`, `bars_h`, `shaded`, `error_bars`, `error_bars_h`,
  `inf_lines`, `vlines`, `hlines`, `histogram`, `histogram2d`, `heatmap`,
  `image`, `pie_chart`, `text`, `annotation`, `dummy`

## Quick start

```python
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()

h.set_data(y * 0.8)
p.render()
```

## API

```python
import nbimplot as ip

p = ip.Plot(width=900, height=450, title="Signal")
h = p.line("mid", y, x_axis="x1", y_axis="y1", subplot_index=0)
h.set_data(y_new)
p.render()
p.show()
```

`prefer_implot=False` and `strict_wasm=False` are not supported.

Additional 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_plot_flags(...)` for native ImPlot flags (`no_legend`, `no_menus`, etc.)
- `p.set_axis_state("x2|x3|y2|y3", enabled=True|False, scale="linear|log|time")`
- `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.set_secondary_axes(x2=..., x3=..., y2=..., y3=...)`
- `p.set_time_axis("x1|x2|x3|y1|y2|y3")`
- `p.set_colormap("Deep|Dark|Pastel|Paired|Viridis|Plasma|Hot|Cool|Pink|Jet|Twilight|RdBu|BrBG|PiYG|Spectral|Greys")`
- Most primitive APIs support `x_axis="x1|x2|x3"` and `y_axis="y1|y2|y3"`
- `p.line(..., color=\"#3b82f6\", line_weight=2.0, marker=\"circle\", marker_size=5.0)` and `handle.set_style(...)`
- `p.stream_line(..., capacity=N)` and `handle.append(...)` for in-place ring-buffer style updates
- `p.hide_next_item()` to hide the next added series/primitive without removing it
- `p.heatmap(..., label_fmt="%.2f", scale_min=None, scale_max=None)` and `label_fmt=""` to hide cell-value text
- `p.heatmap(..., show_colorbar=True, colorbar_label="Intensity", colorbar_format="%g", colorbar_flags=0)`
- `p.histogram2d(..., show_colorbar=True, colorbar_label="Count", colorbar_format="%g", colorbar_flags=0)`
- `p.image(..., bounds=((x0, y0), (x1, y1)), uv0=(0, 0), uv1=(1, 1))` for true `ImPlot::PlotImage` rendering (`z` can be 2D grayscale or `H x W x 3/4` RGB(A))
- `p.tag_x(...)`, `p.tag_y(...)`, `p.colormap_slider(...)`, `p.colormap_button(...)`, `p.colormap_selector(...)`
- `p.drag_drop_plot(...)`, `p.drag_drop_axis(...)`, `p.drag_drop_legend(...)`
- `p.on_perf_stats(callback, interval_ms=500)` for live FPS/WASM timings
- `p.on_tool_change(callback)` for drag tool updates
- `p.on_selection_change(callback)` for box-selection/query updates

Interactive tools:

- `p.drag_line_x(...)`
- `p.drag_line_y(...)`
- `p.drag_point(...)`
- `p.drag_rect(...)`

## Subplots (Native ImPlot)

```python
import nbimplot as ip
import numpy as np

sp = ip.Subplots(
    2,
    2,
    title="Dashboard",
    width=1000,
    height=700,
    link_rows=True,
    link_cols=True,
    share_items=True,
)
sp.subplot(0, 0).line("line", np.sin(np.linspace(0, 20, 5000, dtype=np.float32)))
sp.subplot(0, 1).scatter("pts", np.random.randn(2000).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=40)
sp.show()
```

`Subplots(...)` supports axis-linking controls:

- `link_rows`: link y-axis within each row
- `link_cols`: link x-axis within each column
- `link_all_x`: link x-axis for all cells
- `link_all_y`: link y-axis for all cells
- Additional compatibility flags: `share_items`, `no_legend`, `no_menus`,
  `no_resize`, `no_align`, `col_major`

Aligned-group helper:

- `ip.AlignedPlots(rows, cols, group_id="aligned", vertical=True, ...)`

## Example Notebook

- `notebooks/nbimplot_examples.ipynb`
- `notebooks/nbimplot_api_gallery.ipynb`
- `notebooks/nbimplot_benchmarks.ipynb`

## Troubleshooting

If JupyterLab shows:

`Failed to load model class 'AnyModel' from module 'anywidget'`

this is typically an environment/frontend mismatch (server env vs kernel env) or
an old/stale anywidget install. Fix with:

```bash
python -m pip install -U "nbimplot>=0.1.2" "anywidget>=0.9.21" ipywidgets jupyterlab_widgets
```

Then fully restart the JupyterLab server (not just kernel restart).

Quick verification in a notebook cell:

```python
import nbimplot as ip, anywidget, sys
print("python:", sys.executable)
print("anywidget:", anywidget.__version__)
print("has Plot:", hasattr(ip, "Plot"))
```

## Build WASM core

### Prerequisites

- Emscripten SDK (`emcmake`, `emcc`)
- CMake >= 3.20

### Build (WASM core only)

```bash
scripts/build_wasm.sh
```

Outputs are written to `nbimplot/wasm/`:

- `nbimplot_wasm.js`
- `nbimplot_wasm.wasm`

If outputs are missing or invalid, the widget reports a runtime error.

## Build with ImPlot sources

ImPlot is required for runtime. Build with Dear ImGui + ImPlot sources:

```bash
NBIMPLOT_WITH_IMPLOT=ON \
NBIMPLOT_IMGUI_DIR=/path/to/imgui \
NBIMPLOT_IMPLOT_DIR=/path/to/implot \
scripts/build_wasm.sh
```

If you cloned deps into `third_party/imgui` and `third_party/implot`, this also works:

```bash
NBIMPLOT_WITH_IMPLOT=ON scripts/build_wasm.sh
```

Notes:

- `wasm/core/nbimplot_implot_layer.cpp` creates ImGui/ImPlot contexts when compiled with `NBIMPLOT_WITH_IMPLOT=1`.
- The final WebGL backend draw hookup is intentionally isolated behind the `ImPlotLayer::render(...)` seam.

## Performance behavior

- Raw rendering path when visible points `<= 3 * pixel_width`
- Min/max bucket LOD path when visible points `> 3 * pixel_width`
- LOD decision and output generation happen in WASM
