@@ -63,18 +63,18 @@ Rule 5 – Text never clips. Optional gutters earn real layout space:
6363| ` drawScaleBar2d ` / ` drawColorbar2d ` | 3076 / 3356 |
6464| ** Floating keys** : ` _keyEnsure ` / ` _keyRect ` / ` drawKeys ` | 3175 / 3198 / 3211 |
6565| ` _drawAxes2d ` (ticks, labels, title) | 3468 |
66- | ` drawOverlay2d ` / ` drawMarkers2d ` | 3621 / 3785 |
66+ | ` drawOverlay2d ` / ` drawMarkers2d ` | 3623 / 3787 |
6767| ** Image layers** : ` _layerBytes ` / ` _layerBitmap ` / ` _drawLayers2d ` | 2701 / 2725 / 2786 |
6868| Binary-bytes splice: ` _spliceBinaryBytes ` / ` _registerBinaryPixelListeners ` | 849 / 880 |
69- | ** Hover readout** : ` _pixelValue2d ` / ` _readoutInfo2d ` | 4629 / 4714 |
70- | ` _notifyReadout ` / ` _updateStatus2d ` / ` _armValueProbe ` | 4753 / 4768 / 4789 |
71- | ** 3D drawing** : ` draw3d ` | 5765 |
72- | Event emission ` _emitEvent ` | 6602 |
73- | 3D event handlers ` _attachEvents3d ` | 6659 |
74- | ** 1D drawing** : ` draw1d ` | 6883 |
75- | ` _drawLine ` (1D series + markers) | 7036 |
76- | ` drawOverlay1d ` / ` drawMarkers1d ` | 7329 / 7413 |
77- | Marker hit-test ` _markerHitTest2d ` | 7680 |
69+ | ** Hover readout** : ` _pixelValue2d ` / ` _readoutInfo2d ` | 4631 / 4716 |
70+ | ` _notifyReadout ` / ` _updateStatus2d ` / ` _armValueProbe ` | 4755 / 4770 / 4791 |
71+ | ** 3D drawing** : ` draw3d ` | 5767 |
72+ | Event emission ` _emitEvent ` | 6604 |
73+ | 3D event handlers ` _attachEvents3d ` | 6661 |
74+ | ** 1D drawing** : ` draw1d ` | 6885 |
75+ | ` _drawLine ` (1D series + markers) | 7038 |
76+ | ` drawOverlay1d ` / ` drawMarkers1d ` | 7331 / 7415 |
77+ | Marker hit-test ` _markerHitTest2d ` | 7682 |
7878
7979> ** ` raster ` marker (1D/PlotXY)** — ` drawMarkers1d ` has a ` type==='raster' `
8080> branch that blits a single RGBA image across data-coord ` extent ` (the fast
@@ -83,22 +83,22 @@ Rule 5 – Text never clips. Optional gutters earn real layout space:
8383> redraws never re-transmit them; the decoded ` OffscreenCanvas ` is cached on
8484> the marker set (` ms._rasterBmp ` /` _rasterKey ` ). The shared ` clip_path ` block
8585> clips it to a curved sector.
86- | Panel event dispatch ` _attachPanelEvents ` | 7937 |
87- | 2D events ` _attachEvents2d ` | 8004 |
88- | 1D events ` _attachEvents1d ` | 8390 |
89- | 2D widget drag ` _ovHitTest2d ` / ` _doDrag2d ` | 8665 / 8944 |
90- | ** Brush strokes** : ` _brushLiveBegin ` / ` _brushCommit ` / ` _brushErase ` / ` _brushPaintAt ` | 8857 / 8871 / 8900 / 8935 |
91- | 1D widget drag ` _canvasXToFrac1d ` … / snapping ` _snapVal ` | 9069 / 9142 |
92- | Shared-axis propagation ` _getShareGroups ` | 9213 |
93- | Figure resize ` _applyFigResizeDOM ` | 9277 |
94- | ** Bar chart** : ` _barGeom ` / ` drawBar ` / ` _attachEventsBar ` | 9471 / 9534 / 9910 |
95- | Generic redraw ` _redrawPanel ` | 10100 |
96- | ** PNG export** : ` _compositeCanvas ` / ` exportCanvas ` / ` exportPNG ` | 10263 / 10459 / 10518 |
97- | Native-resolution render ` _withNativeSize ` | 10239 |
98- | ** Export UI** : ` _toast ` / ` _downloadCanvas ` / ` _openMenu ` | 10552 / 10661 / 10840 |
99- | Export registry ` registerExportAction ` | 10717 |
100- | ** Embedding API** : ` createLocalModel ` / ` mount ` | 11231 / 11287 |
101- | ** Navigated embed** : ` decodeBlocks ` / ` mountNavigated ` | 11542 / 11929 |
86+ | Panel event dispatch ` _attachPanelEvents ` | 7939 |
87+ | 2D events ` _attachEvents2d ` | 8025 |
88+ | 1D events ` _attachEvents1d ` | 8411 |
89+ | 2D widget drag ` _ovHitTest2d ` / ` _doDrag2d ` | 8686 / 8965 |
90+ | ** Brush strokes** : ` _brushLiveBegin ` / ` _brushCommit ` / ` _brushErase ` / ` _brushPaintAt ` | 8878 / 8892 / 8921 / 8956 |
91+ | 1D widget drag ` _canvasXToFrac1d ` … / snapping ` _snapVal ` | 9090 / 9163 |
92+ | Shared-axis propagation ` _getShareGroups ` | 9234 |
93+ | Figure resize ` _applyFigResizeDOM ` | 9298 |
94+ | ** Bar chart** : ` _barGeom ` / ` drawBar ` / ` _attachEventsBar ` | 9492 / 9555 / 9931 |
95+ | Generic redraw ` _redrawPanel ` | 10121 |
96+ | ** PNG export** : ` _compositeCanvas ` / ` exportCanvas ` / ` exportPNG ` | 10284 / 10480 / 10539 |
97+ | Native-resolution render ` _withNativeSize ` | 10260 |
98+ | ** Export UI** : ` _toast ` / ` _downloadCanvas ` / ` _openMenu ` | 10573 / 10682 / 10861 |
99+ | Export registry ` registerExportAction ` | 10738 |
100+ | ** Embedding API** : ` createLocalModel ` / ` mount ` | 11252 / 11308 |
101+ | ** Navigated embed** : ` decodeBlocks ` / ` mountNavigated ` | 11563 / 11950 |
102102
103103> ** ` brush ` widget (2-D)** — the one widget whose drag is * modal* , and the one
104104> that must NOT write the model per tick. ` _ovHitTest2d ` takes an extra ` mods `
@@ -287,8 +287,8 @@ st.colorbar_label_size (label font sizes; optional)
287287| ` drawScaleBar2d(p) ` | 3076 | Physical scale bar |
288288| ` _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 |
289289| ` 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) |
290- | ` _drawAxes2d(p) ` | 3468 | Ticks ( edge labels nudged inward both axes), axis labels + title via ` _drawTex ` |
291- | ` drawOverlay2d(p) ` / ` drawMarkers2d(p) ` | 3621 / 3785 | Widgets / marker groups |
290+ | ` _drawAxes2d(p) ` | 3468 | Ticks placed through ` _imgToCanvas2d ` (pixel-centre axes, letterbox-aware; edge labels nudged inward both axes), axis labels + title via ` _drawTex ` |
291+ | ` drawOverlay2d(p) ` / ` drawMarkers2d(p) ` | 3623 / 3787 | Widgets / marker groups |
292292
293293Zoom model: at ` zoom=1 ` the whole image fills the fit-rect; at ` zoom=Z>1 ` a
294294` 1/Z ` region fills it. ` _imgToCanvas2d ` / ` _canvasToImg2d ` must stay exact
@@ -301,6 +301,14 @@ coordinate to `xdata`/`ydata` through `_imgToAxisVal2d` (imshow axes are
301301per-pixel centres, pcolormesh axes cell edges). Code that addresses texels or
302302the detail region by pixel EDGE (` _pixelValue2d ` ) adds the ` 0.5 ` back itself.
303303
304+ The tick gutters (` _drawAxes2d ` ) use the same chain in reverse: tick value →
305+ ` _axisValToImg2d ` (imshow; pcolormesh goes through ` _axisValToFrac ` × n − ½) →
306+ ` _imgToCanvas2d ` , so ticks honour the letterboxed fit rect, zoom < 1, pan
307+ clamping and descending (` origin='lower' ` ) axes for free. The visible value
308+ range is the gutter's two ends through ` _canvasToImg2d ` , clamped to the outer
309+ pixel edges. Python's ` Plot2D._axis_extent_frac ` (behind ` set_view ` /
310+ ` set_xlim ` ) mirrors ` _axisValToImg2d ` — keep the two in step.
311+
304312` texts ` markers (1-D and 2-D) set their font and optional outline through
305313` _markerTextStyle(ctx, ms) ` (` fontweight ` , ` outline_color ` , ` outline_width ` )
306314and draw via ` _drawMarkerText ` , which strokes the halo under the fill.
@@ -652,13 +660,13 @@ exportCanvas(same opts) → {canvas, width, height} // synchronous, throws
652660
653661| Function | Line | Purpose |
654662| ----------| ------| ---------|
655- | ` _cssScale ` | 10135 | inverse of ` _applyScale ` 's ` transform:scale() ` |
656- | ` _panelBox ` | 10146 | the element whose rect bounds one panel |
657- | ` _neutralizeView ` / ` _restoreView ` | 10155 / 10180 | transient whole-extent view |
658- | ` _nativeGeom ` / ` _nativeGuard ` | 10195 / 10214 | native size + why-not message |
659- | ` _withNativeSize ` | 10239 | resize → redraw → run → restore |
660- | ` _compositeCanvas ` | 10263 | the compositor (` _drawEl ` / ` _drawPanel ` …) |
661- | ` exportCanvas ` / ` exportPNG ` | 10459 / 10518 | orchestrator / data-URL wrapper |
663+ | ` _cssScale ` | 10156 | inverse of ` _applyScale ` 's ` transform:scale() ` |
664+ | ` _panelBox ` | 10167 | the element whose rect bounds one panel |
665+ | ` _neutralizeView ` / ` _restoreView ` | 10176 / 10201 | transient whole-extent view |
666+ | ` _nativeGeom ` / ` _nativeGuard ` | 10216 / 10235 | native size + why-not message |
667+ | ` _withNativeSize ` | 10260 | resize → redraw → run → restore |
668+ | ` _compositeCanvas ` | 10284 | the compositor (` _drawEl ` / ` _drawPanel ` …) |
669+ | ` exportCanvas ` / ` exportPNG ` | 10480 / 10539 | orchestrator / data-URL wrapper |
662670
663671** The whole pipeline is ONE synchronous task** — theme swap, view reset, native
664672resize, composite, restore — so the browser never paints an intermediate state
@@ -754,13 +762,13 @@ leaders that cross into the panel included. Pinned by
754762
755763| Function | Line | Purpose |
756764| ----------| ------| ---------|
757- | ` _toast ` | 10552 | transient bottom-centre message |
758- | ` _copyCanvas ` | 10587 | clipboard write + feature detection |
759- | ` _showPngPreview ` | 10611 | framed-document download fallback |
760- | ` _downloadCanvas ` | 10661 | ` <a download> ` or the preview |
761- | ` registerExportAction ` | 10717 | downstream extension point |
762- | ` _menuRows ` / ` _openMenu ` | 10771 / 10840 | menu model / DOM |
763- | ` _panelAtPoint ` | 10952 | hit test (insets first — they sit on top) |
765+ | ` _toast ` | 10573 | transient bottom-centre message |
766+ | ` _copyCanvas ` | 10608 | clipboard write + feature detection |
767+ | ` _showPngPreview ` | 10632 | framed-document download fallback |
768+ | ` _downloadCanvas ` | 10682 | ` <a download> ` or the preview |
769+ | ` registerExportAction ` | 10738 | downstream extension point |
770+ | ` _menuRows ` / ` _openMenu ` | 10792 / 10861 | menu model / DOM |
771+ | ` _panelAtPoint ` | 10973 | hit test (insets first — they sit on top) |
764772
765773- ** An ` exportBtn ` badge (⤓, beside the help badge) opens the same menu on an
766774 ordinary left click.** It is a ` role="button" ` with ` tabIndex=0 ` and
@@ -844,16 +852,16 @@ bindings, let it dispatch", rather than a hand-written program per result kind.
844852
845853| Function | Line | Purpose |
846854| ----------| ------| ---------|
847- | ` decodeBlocks ` | 11542 | one base64 ` fetch ` → one ArrayBuffer → a typed-array view per manifest entry |
848- | ` dense ` | 11568 | ` at ` / ` gather ` / ` reduce ` over a block whose leading axes are the nav axes |
849- | ` ragged ` | 11633 | the same three, over a row-pointer block (` offsets ` + one array per column) |
850- | ` maskFromWidget ` | 11716 | rectangle / circle / annulus widget dict → ` Uint8Array ` (carries ` width ` /` height ` ) |
851- | ` rasterDisks ` | 11756 | splat ` {x, y, intensity} ` rows as filled disks — the base image of a vectors panel |
852- | ` robustLevels ` / ` toU8 ` | 11785 / 11826 | the percentile window and the 8-bit code map, one implementation |
853- | ` panelAxis ` | 11904 | a 1-D panel's decoded x axis (` _1dXArr ` , else ` x_axis_b64 ` ) |
854- | ` installTouchShim ` / ` reportEmbedHeight ` | 11841 / 11860 | page chrome: touch → mouse, ` postMessage({aplEmbedHeight}) ` |
855- | ` encodeBase64 ` / ` typedArrayBytes ` | 11880 / 11888 | a 3-D cloud's geometry channel is base64, not the binary side table |
856- | ` mountNavigated ` | 11929 | mount + bind + dispatch; resolves to the mount handle plus ` dispatch ` /` index ` /` blocks ` |
855+ | ` decodeBlocks ` | 11563 | one base64 ` fetch ` → one ArrayBuffer → a typed-array view per manifest entry |
856+ | ` dense ` | 11589 | ` at ` / ` gather ` / ` reduce ` over a block whose leading axes are the nav axes |
857+ | ` ragged ` | 11654 | the same three, over a row-pointer block (` offsets ` + one array per column) |
858+ | ` maskFromWidget ` | 11737 | rectangle / circle / annulus widget dict → ` Uint8Array ` (carries ` width ` /` height ` ) |
859+ | ` rasterDisks ` | 11777 | splat ` {x, y, intensity} ` rows as filled disks — the base image of a vectors panel |
860+ | ` robustLevels ` / ` toU8 ` | 11806 / 11847 | the percentile window and the 8-bit code map, one implementation |
861+ | ` panelAxis ` | 11925 | a 1-D panel's decoded x axis (` _1dXArr ` , else ` x_axis_b64 ` ) |
862+ | ` installTouchShim ` / ` reportEmbedHeight ` | 11862 / 11881 | page chrome: touch → mouse, ` postMessage({aplEmbedHeight}) ` |
863+ | ` encodeBase64 ` / ` typedArrayBytes ` | 11901 / 11909 | a 3-D cloud's geometry channel is base64, not the binary side table |
864+ | ` mountNavigated ` | 11950 | mount + bind + dispatch; resolves to the mount handle plus ` dispatch ` /` index ` /` blocks ` |
857865
858866` mountNavigated(el, page, opts) ` is ** async** — the blob decode is a ` fetch ` of
859867a ` data: ` URL — so a host ` await ` s it. ` page ` is `{state, blocks, bindings,
0 commit comments