Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ grep -nE '^\s*(function|const|let) [A-Za-z_]' anyplotlib/figure_esm.js
```

and reconcile against the two numbered tables (the section map near the top and
the 2-D function table). Both were last verified at 12,349 lines.
the 2-D function table). Both were last verified at 12,386 lines.

Changelog entries: add a fragment file to `upcoming_changes/` (e.g.
`123.new_feature.rst`) — towncrier assembles `CHANGELOG.rst` at release time.
Expand Down
157 changes: 84 additions & 73 deletions anyplotlib/FIGURE_ESM.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,31 +50,31 @@ Rule 5 – Text never clips. Optional gutters earn real layout space:
| Shared plot-area padding (`PAD_*`) | 9 |
| Theme (dark/light detection) | 26 |
| Shared math helpers | 64 |
| b64 array decode helpers | 109 |
| **Rich-text (mini-TeX) engine**: `_texRuns` / `_texLayout` / `_drawTex` | 175 / 242 / 264 |
| **2D gutter geometry**: `_cbLabelW` / `_cbValueChars` / `_cbTickW` / `_cbWidth` / `_cbGap` / `_padT` / `_titlePx` | 313 / 324 / 342 / 354 / 364 / 374 / 384 |
| **Layout engine** `applyLayout` | 875 |
| `_buildCanvasStack` | 958 |
| `_createPanelDOM` | 1100 |
| `_createInsetDOM` / `_applyAllInsetStates` | 1241 / 1635 |
| `_resizePanelDOM` | 2348 |
| **2D drawing**: `_imgFitRect` | 2519 |
| `draw2d` | 2863 |
| `drawScaleBar2d` / `drawColorbar2d` | 3058 / 3338 |
| **Floating keys**: `_keyEnsure` / `_keyRect` / `drawKeys` | 3157 / 3180 / 3193 |
| `_drawAxes2d` (ticks, labels, title) | 3450 |
| `drawOverlay2d` / `drawMarkers2d` | 3603 / 3767 |
| **Image layers**: `_layerBytes` / `_layerBitmap` / `_drawLayers2d` | 2683 / 2707 / 2768 |
| Binary-bytes splice: `_spliceBinaryBytes` / `_registerBinaryPixelListeners` | 831 / 862 |
| **Hover readout**: `_pixelValue2d` / `_readoutInfo2d` | 4611 / 4693 |
| `_notifyReadout` / `_updateStatus2d` / `_armValueProbe` | 4733 / 4748 / 4769 |
| **3D drawing**: `draw3d` | 5745 |
| Event emission `_emitEvent` | 6582 |
| 3D event handlers `_attachEvents3d` | 6639 |
| **1D drawing**: `draw1d` | 6863 |
| `_drawLine` (1D series + markers) | 7016 |
| `drawOverlay1d` / `drawMarkers1d` | 7309 / 7393 |
| Marker hit-test `_markerHitTest2d` | 7661 |
| b64 array decode helpers | 141 |
| **Rich-text (mini-TeX) engine**: `_texRuns` / `_texLayout` / `_drawTex` | 193 / 260 / 282 |
| **2D gutter geometry**: `_cbLabelW` / `_cbValueChars` / `_cbTickW` / `_cbWidth` / `_cbGap` / `_padT` / `_titlePx` | 331 / 342 / 360 / 372 / 382 / 392 / 402 |
| **Layout engine** `applyLayout` | 893 |
| `_buildCanvasStack` | 976 |
| `_createPanelDOM` | 1118 |
| `_createInsetDOM` / `_applyAllInsetStates` | 1259 / 1653 |
| `_resizePanelDOM` | 2366 |
| **2D drawing**: `_imgFitRect` | 2537 |
| `draw2d` | 2881 |
| `drawScaleBar2d` / `drawColorbar2d` | 3076 / 3356 |
| **Floating keys**: `_keyEnsure` / `_keyRect` / `drawKeys` | 3175 / 3198 / 3211 |
| `_drawAxes2d` (ticks, labels, title) | 3468 |
| `drawOverlay2d` / `drawMarkers2d` | 3621 / 3785 |
| **Image layers**: `_layerBytes` / `_layerBitmap` / `_drawLayers2d` | 2701 / 2725 / 2786 |
| Binary-bytes splice: `_spliceBinaryBytes` / `_registerBinaryPixelListeners` | 849 / 880 |
| **Hover readout**: `_pixelValue2d` / `_readoutInfo2d` | 4629 / 4714 |
| `_notifyReadout` / `_updateStatus2d` / `_armValueProbe` | 4753 / 4768 / 4789 |
| **3D drawing**: `draw3d` | 5765 |
| Event emission `_emitEvent` | 6602 |
| 3D event handlers `_attachEvents3d` | 6659 |
| **1D drawing**: `draw1d` | 6883 |
| `_drawLine` (1D series + markers) | 7036 |
| `drawOverlay1d` / `drawMarkers1d` | 7329 / 7413 |
| Marker hit-test `_markerHitTest2d` | 7680 |

> **`raster` marker (1D/PlotXY)** — `drawMarkers1d` has a `type==='raster'`
> branch that blits a single RGBA image across data-coord `extent` (the fast
Expand All @@ -83,22 +83,22 @@ Rule 5 – Text never clips. Optional gutters earn real layout space:
> redraws never re-transmit them; the decoded `OffscreenCanvas` is cached on
> the marker set (`ms._rasterBmp`/`_rasterKey`). The shared `clip_path` block
> clips it to a curved sector.
| Panel event dispatch `_attachPanelEvents` | 7918 |
| 2D events `_attachEvents2d` | 7960 |
| 1D events `_attachEvents1d` | 8353 |
| 2D widget drag `_ovHitTest2d` / `_doDrag2d` | 8628 / 8907 |
| **Brush strokes**: `_brushLiveBegin` / `_brushCommit` / `_brushErase` / `_brushPaintAt` | 8820 / 8834 / 8863 / 8898 |
| 1D widget drag `_canvasXToFrac1d` … / snapping `_snapVal` | 9032 / 9105 |
| Shared-axis propagation `_getShareGroups` | 9176 |
| Figure resize `_applyFigResizeDOM` | 9240 |
| **Bar chart**: `_barGeom` / `drawBar` / `_attachEventsBar` | 9434 / 9497 / 9873 |
| Generic redraw `_redrawPanel` | 10063 |
| **PNG export**: `_compositeCanvas` / `exportCanvas` / `exportPNG` | 10226 / 10422 / 10481 |
| Native-resolution render `_withNativeSize` | 10202 |
| **Export UI**: `_toast` / `_downloadCanvas` / `_openMenu` | 10515 / 10624 / 10803 |
| Export registry `registerExportAction` | 10680 |
| **Embedding API**: `createLocalModel` / `mount` | 11194 / 11250 |
| **Navigated embed**: `decodeBlocks` / `mountNavigated` | 11505 / 11892 |
| Panel event dispatch `_attachPanelEvents` | 7937 |
| 2D events `_attachEvents2d` | 8004 |
| 1D events `_attachEvents1d` | 8390 |
| 2D widget drag `_ovHitTest2d` / `_doDrag2d` | 8665 / 8944 |
| **Brush strokes**: `_brushLiveBegin` / `_brushCommit` / `_brushErase` / `_brushPaintAt` | 8857 / 8871 / 8900 / 8935 |
| 1D widget drag `_canvasXToFrac1d` … / snapping `_snapVal` | 9069 / 9142 |
| Shared-axis propagation `_getShareGroups` | 9213 |
| Figure resize `_applyFigResizeDOM` | 9277 |
| **Bar chart**: `_barGeom` / `drawBar` / `_attachEventsBar` | 9471 / 9534 / 9910 |
| Generic redraw `_redrawPanel` | 10100 |
| **PNG export**: `_compositeCanvas` / `exportCanvas` / `exportPNG` | 10263 / 10459 / 10518 |
| Native-resolution render `_withNativeSize` | 10239 |
| **Export UI**: `_toast` / `_downloadCanvas` / `_openMenu` | 10552 / 10661 / 10840 |
| Export registry `registerExportAction` | 10717 |
| **Embedding API**: `createLocalModel` / `mount` | 11231 / 11287 |
| **Navigated embed**: `decodeBlocks` / `mountNavigated` | 11542 / 11929 |

> **`brush` widget (2-D)** — the one widget whose drag is *modal*, and the one
> that must NOT write the model per tick. `_ovHitTest2d` takes an extra `mods`
Expand Down Expand Up @@ -282,17 +282,28 @@ st.colorbar_label_size (label font sizes; optional)

| Function | Line | Purpose |
|----------|------|---------|
| **`_imgFitRect(iw,ih,cw,ch)`** / `_cbFitRect(st,imgW,imgH)` | **2519 / 2528** | Largest rect of aspect `iw:ih` centred in `cw×ch`; all 2-D coordinate functions derive from this. `_cbFitRect` is its vertical extent in whole px — the colorbar strip spans the letterboxed IMAGE, not the whole image area (`p._cbH`) |
| `draw2d(p)` | 2863 | Main render: `_resizePanelDOM` → decode → LUT → ImageBitmap → blit; then mask, axes, scale bar, colorbar, overlay, markers |
| `drawScaleBar2d(p)` | 3058 | Physical scale bar |
| `_rawBand(st)` / `_displayFrac(st, val)` / `_buildLut32(st)` | 2578 / 2588 / 2596 | The quantisation band the u8 bytes were encoded over, the ONE rule mapping a value through the display window and scale mode to a colormap fraction (shared by the LUT and the colorbar strip, so the strip's colours are the image's), then the 256-entry LUT built from both. `_rawBand` mirrors Python `_tile_quant_clim`: a DEGENERATE band (`raw_max <= raw_min`) is UNSET and falls back to `display_min/max`. Both render paths and the colorbar go through it — honouring a `(0, 0)` band paints solid black |
| `drawColorbar2d(p)` | 3338 | Gradient strip coloured through the display window (`_displayFrac` — saturated beyond it, like the image) + min/max marks (band-relative, via `_rawBand`) + the two values written beside them (`fmtRange`; kept inside the strip by the glyphs' measured extent, held apart on a tiny range, the maximum alone at its own place on a strip too short for both; omitted when the layout dropped the gutter, `p._cbTickW == 0`) + rotated label centred right after the values' measured width (never past the reserved gutter) |
| `_drawAxes2d(p)` | 3450 | Ticks (edge labels nudged inward both axes), axis labels + title via `_drawTex` |
| `drawOverlay2d(p)` / `drawMarkers2d(p)` | 3603 / 3767 | Widgets / marker groups |
| **`_imgFitRect(iw,ih,cw,ch)`** / `_cbFitRect(st,imgW,imgH)` | **2537 / 2546** | Largest rect of aspect `iw:ih` centred in `cw×ch`; all 2-D coordinate functions derive from this. `_cbFitRect` is its vertical extent in whole px — the colorbar strip spans the letterboxed IMAGE, not the whole image area (`p._cbH`) |
| `draw2d(p)` | 2881 | Main render: `_resizePanelDOM` → decode → LUT → ImageBitmap → blit; then mask, axes, scale bar, colorbar, overlay, markers |
| `drawScaleBar2d(p)` | 3076 | Physical scale bar |
| `_rawBand(st)` / `_displayFrac(st, val)` / `_buildLut32(st)` | 2596 / 2606 / 2614 | The quantisation band the u8 bytes were encoded over, the ONE rule mapping a value through the display window and scale mode to a colormap fraction (shared by the LUT and the colorbar strip, so the strip's colours are the image's), then the 256-entry LUT built from both. `_rawBand` mirrors Python `_tile_quant_clim`: a DEGENERATE band (`raw_max <= raw_min`) is UNSET and falls back to `display_min/max`. Both render paths and the colorbar go through it — honouring a `(0, 0)` band paints solid black |
| `drawColorbar2d(p)` | 3356 | Gradient strip coloured through the display window (`_displayFrac` — saturated beyond it, like the image) + min/max marks (band-relative, via `_rawBand`) + the two values written beside them (`fmtRange`; kept inside the strip by the glyphs' measured extent, held apart on a tiny range, the maximum alone at its own place on a strip too short for both; omitted when the layout dropped the gutter, `p._cbTickW == 0`) + rotated label centred right after the values' measured width (never past the reserved gutter) |
| `_drawAxes2d(p)` | 3468 | Ticks (edge labels nudged inward both axes), axis labels + title via `_drawTex` |
| `drawOverlay2d(p)` / `drawMarkers2d(p)` | 3621 / 3785 | Widgets / marker groups |

Zoom model: at `zoom=1` the whole image fills the fit-rect; at `zoom=Z>1` a
`1/Z` region fills it. `_imgToCanvas2d` / `_canvasToImg2d` must stay exact
inverses of the blit geometry.
inverses of the blit geometry — and of each other. Both use the pixel-CENTRE
convention (integer *i* is the centre of pixel *i*, which spans
`[i − 0.5, i + 0.5)`), the same as marker offsets, widget positions and Python's
`data_to_display` / `display_to_data`; `_imgPix2d` / `_inImgAxis2d` name the
pixel under a coordinate and bound it. Pointer events and the readout map a
coordinate to `xdata`/`ydata` through `_imgToAxisVal2d` (imshow axes are
per-pixel centres, pcolormesh axes cell edges). Code that addresses texels or
the detail region by pixel EDGE (`_pixelValue2d`) adds the `0.5` back itself.

`texts` markers (1-D and 2-D) set their font and optional outline through
`_markerTextStyle(ctx, ms)` (`fontweight`, `outline_color`, `outline_width`)
and draw via `_drawMarkerText`, which strokes the halo under the fill.

### Hover readout

Expand Down Expand Up @@ -641,13 +652,13 @@ exportCanvas(same opts) → {canvas, width, height} // synchronous, throws

| Function | Line | Purpose |
|----------|------|---------|
| `_cssScale` | 10098 | inverse of `_applyScale`'s `transform:scale()` |
| `_panelBox` | 10109 | the element whose rect bounds one panel |
| `_neutralizeView` / `_restoreView` | 10118 / 10143 | transient whole-extent view |
| `_nativeGeom` / `_nativeGuard` | 10158 / 10177 | native size + why-not message |
| `_withNativeSize` | 10202 | resize → redraw → run → restore |
| `_compositeCanvas` | 10226 | the compositor (`_drawEl` / `_drawPanel` …) |
| `exportCanvas` / `exportPNG` | 10422 / 10481 | orchestrator / data-URL wrapper |
| `_cssScale` | 10135 | inverse of `_applyScale`'s `transform:scale()` |
| `_panelBox` | 10146 | the element whose rect bounds one panel |
| `_neutralizeView` / `_restoreView` | 10155 / 10180 | transient whole-extent view |
| `_nativeGeom` / `_nativeGuard` | 10195 / 10214 | native size + why-not message |
| `_withNativeSize` | 10239 | resize → redraw → run → restore |
| `_compositeCanvas` | 10263 | the compositor (`_drawEl` / `_drawPanel` …) |
| `exportCanvas` / `exportPNG` | 10459 / 10518 | orchestrator / data-URL wrapper |

**The whole pipeline is ONE synchronous task** — theme swap, view reset, native
resize, composite, restore — so the browser never paints an intermediate state
Expand Down Expand Up @@ -743,13 +754,13 @@ leaders that cross into the panel included. Pinned by

| Function | Line | Purpose |
|----------|------|---------|
| `_toast` | 10515 | transient bottom-centre message |
| `_copyCanvas` | 10550 | clipboard write + feature detection |
| `_showPngPreview` | 10574 | framed-document download fallback |
| `_downloadCanvas` | 10624 | `<a download>` or the preview |
| `registerExportAction` | 10680 | downstream extension point |
| `_menuRows` / `_openMenu` | 10734 / 10803 | menu model / DOM |
| `_panelAtPoint` | 10915 | hit test (insets first — they sit on top) |
| `_toast` | 10552 | transient bottom-centre message |
| `_copyCanvas` | 10587 | clipboard write + feature detection |
| `_showPngPreview` | 10611 | framed-document download fallback |
| `_downloadCanvas` | 10661 | `<a download>` or the preview |
| `registerExportAction` | 10717 | downstream extension point |
| `_menuRows` / `_openMenu` | 10771 / 10840 | menu model / DOM |
| `_panelAtPoint` | 10952 | hit test (insets first — they sit on top) |

- **An `exportBtn` badge (⤓, beside the help badge) opens the same menu on an
ordinary left click.** It is a `role="button"` with `tabIndex=0` and
Expand Down Expand Up @@ -833,16 +844,16 @@ bindings, let it dispatch", rather than a hand-written program per result kind.

| Function | Line | Purpose |
|----------|------|---------|
| `decodeBlocks` | 11505 | one base64 `fetch` → one ArrayBuffer → a typed-array view per manifest entry |
| `dense` | 11531 | `at` / `gather` / `reduce` over a block whose leading axes are the nav axes |
| `ragged` | 11596 | the same three, over a row-pointer block (`offsets` + one array per column) |
| `maskFromWidget` | 11679 | rectangle / circle / annulus widget dict → `Uint8Array` (carries `width`/`height`) |
| `rasterDisks` | 11719 | splat `{x, y, intensity}` rows as filled disks — the base image of a vectors panel |
| `robustLevels` / `toU8` | 11748 / 11789 | the percentile window and the 8-bit code map, one implementation |
| `panelAxis` | 11867 | a 1-D panel's decoded x axis (`_1dXArr`, else `x_axis_b64`) |
| `installTouchShim` / `reportEmbedHeight` | 11804 / 11823 | page chrome: touch → mouse, `postMessage({aplEmbedHeight})` |
| `encodeBase64` / `typedArrayBytes` | 11843 / 11851 | a 3-D cloud's geometry channel is base64, not the binary side table |
| `mountNavigated` | 11892 | mount + bind + dispatch; resolves to the mount handle plus `dispatch`/`index`/`blocks` |
| `decodeBlocks` | 11542 | one base64 `fetch` → one ArrayBuffer → a typed-array view per manifest entry |
| `dense` | 11568 | `at` / `gather` / `reduce` over a block whose leading axes are the nav axes |
| `ragged` | 11633 | the same three, over a row-pointer block (`offsets` + one array per column) |
| `maskFromWidget` | 11716 | rectangle / circle / annulus widget dict → `Uint8Array` (carries `width`/`height`) |
| `rasterDisks` | 11756 | splat `{x, y, intensity}` rows as filled disks — the base image of a vectors panel |
| `robustLevels` / `toU8` | 11785 / 11826 | the percentile window and the 8-bit code map, one implementation |
| `panelAxis` | 11904 | a 1-D panel's decoded x axis (`_1dXArr`, else `x_axis_b64`) |
| `installTouchShim` / `reportEmbedHeight` | 11841 / 11860 | page chrome: touch → mouse, `postMessage({aplEmbedHeight})` |
| `encodeBase64` / `typedArrayBytes` | 11880 / 11888 | a 3-D cloud's geometry channel is base64, not the binary side table |
| `mountNavigated` | 11929 | mount + bind + dispatch; resolves to the mount handle plus `dispatch`/`index`/`blocks` |

`mountNavigated(el, page, opts)` is **async** — the blob decode is a `fetch` of
a `data:` URL — so a host `await`s it. `page` is `{state, blocks, bindings,
Expand Down
3 changes: 2 additions & 1 deletion anyplotlib/_base_plot.py
Original file line number Diff line number Diff line change
Expand Up @@ -719,7 +719,8 @@ def add_text(self, x, y, s, name=None, *, color="#ff0000",
transform : str, optional
Coordinate system for ``(x, y)``. Default ``"data"``.
**kwargs : dict
Forwarded to :meth:`add_texts` (e.g. ``clip_display``).
Forwarded to :meth:`add_texts` (e.g. ``clip_display``,
``fontweight``, ``outline_color``).

Returns
-------
Expand Down
2 changes: 1 addition & 1 deletion anyplotlib/callbacks.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ class Event:
button — 0=left 1=middle 2=right; None on move/enter/leave/settled
buttons — bitmask of currently held buttons
xdata, ydata — data-space coordinates (None for Plot3D)
img_x, img_y — Plot2D/PlotMesh: position in IMAGE PIXELS (column, row; row 0 at the top, origin already applied), so a handler can index the source array directly. None on other plot types.
img_x, img_y — Plot2D/PlotMesh: position in IMAGE PIXELS (column, row; row 0 at the top, origin already applied), so a handler can index the source array directly. Integer i is the CENTRE of pixel i (as for markers and widgets), so round() names the pixel. None on other plot types.
ray — Plot3D only: {"origin": [...], "direction": [...]}
line_id — Plot1D only: set when pointer is over a line
dwell_ms — pointer_settled only: actual dwell time
Expand Down
Loading
Loading