Feat/image layers#31
Merged
Merged
Conversation
plot2d.add_layer(data, cmap=, alpha=, clim=, visible=) returns a Layer handle (set/set_data/remove); layers draw bottom-up above the base image with the base's exact fit-rect + zoom/pan transform, under axes, markers, and widgets, and composite in Canvas2D even over a WebGPU base. Per-layer LUT bitmaps are cached by (pixels, cmap, clim) so unchanged layers cost nothing on repaint; exportPNG captures layers for free. Heavy pixels ride dynamic geom keys (layer_<id>_b64): _GEOM_KEYS is now a property that unions the fixed base set with live layer keys, the Electron binary route ships each layer as its own PLOTBIN frame, and _spliceBinaryBytes scans by geom prefix instead of a hardcoded key list. resolve_pixel_tokens materialises layer base64 so save_html snapshots stay self-contained. Layers and tile mode exclude each other (enforced both directions). Layer data must match the base (H, W). 30 unit + 5 Playwright tests (incl. alpha-blend pixel assertions and the WebGPU-base + Canvas2D-layer readback case).
add_inset gains anchor=(x_frac, y_frac) as an alternative to the four corners (inset top-left in figure fraction, clamped inside the figure; minimize/maximize/restore still work). inset.indicate_region(parent_plot, (x, y, w, h)) draws the mark_inset look: a dashed rectangle around the region in the parent's DATA coords plus two leader lines from the rect's facing corners to the inset's nearest corners (deterministic loc1/loc2 pairing). clear_indication() removes it; one indication per inset. Indications live in layout_json (round-trip through figure_state / save_html) and render on a figure-level calloutCanvas above panels and insets: the rect re-maps through the parent transform every draw (tracks zoom/pan, clips to the image area), leaders follow the inset's live DOM rect and hide while minimized, and exportPNG composites them last after a forced fresh draw. The parent may be any 2-D panel in the figure, so cross-panel navigator-to-DP callouts work. 19 tests (13 unit + 6 Playwright visual/tracking).
exportPNG now draws inset title bars (they are DOM divs, previously dropped from every export) and snaps element edges to the output pixel grid (rounded left/right edges, not rounded widths) so fractional DPR no longer produces 1px seams between grid panels. Layers: a shape-changing set_data on a layered plot raises a clear ValueError instead of silently stretching stale-sized layers over the new fit rect (mirrors the tile-mode guards); Layer.set accepts clim='auto' to return to data min/max after an explicit clim (None still means unchanged — the docstring now matches the behavior). indicate_region validates its parent belongs to the same figure and that the region is four finite numbers with positive extent; out-of-bounds regions remain allowed (clipping is by design). FIGURE_ESM.md documents all of the above plus a maintainer note that layout indications are rebuilt from _insets_map on every push (no stale-indication path exists until a remove_inset API is added).
…andles
- ArrowWidget: draggable arrow in image-px coords (body move, head re-aim),
add_arrow_widget + add_widget('arrow', ...) dispatch
- show_handles option on every 2-D widget (hides handle dots; drag unchanged)
- Figure edit_chrome / selected_panel traits: JS-local dashed hover outline
per panel + persistent solid selection outline
- figure-background pointer_down (edit mode, clicks outside all panels) via a
new Figure-level CallbackRegistry
- figure_markers_json: figure-fraction annotation layer (text/circle/rect/
arrow) drawn over all panels, draggable under edit_chrome with pointer_up
write-back; always composited into exportPNG while edit chrome/handles
never export (edit-mode export is pixel-identical to non-edit)
…push fixes - circle gains a visible center node (radius node existed); arrow tail handle becomes a reshape node (tail moves, head anchored: x,y+=d, u,v-=d); shaft still moves the whole arrow; endpoint cursors - edit-chrome hover/selection outlines drawn fully inset (outline-offset = -width) so edge panels are no longer clipped right/bottom - panel drag-swap: per-panel move grip under edit_chrome; dropping on another panel fires figure-level callbacks with source/target panel ids (host swaps and rebuilds); source/empty drops cancel cleanly - mount() gains an onResize hook (rAF-debounced ResizeObserver) so hosts can relayout on container-only resizes - exportPNG(includeWidgets) redraws panel overlay widgets handle-free on a scratch canvas (mirrors the figure-marker export path) — visible resize nodes never bake into harvested PNGs; live canvas untouched - standalone page makeModel(): save_changes() after a parent awi_state push now fires ONLY the listeners of keys actually set (per-key dirty set) — a targeted event_json widget update was being clobbered by the stale panel_<id>_json listener re-applying old panel state in the same cascade (Python-pushed widget.set(color/geometry) never repainted in Electron)
The figure-level callout overlay (calloutCanvas, added in the inset-callout work) and the figure-marker overlay (figMarkerCanvas) sized their backing store to the FULL figure even when there was nothing to draw — both draw functions set width/height to fig_width/fig_height and only THEN returned early on an empty indication/marker list. An empty full-figure transparent canvas spans the whole figure, so it is wider than any single panel and becomes the LARGEST <canvas> in the DOM. Any consumer/test that samples "the largest canvas" then reads the transparent overlay instead of the panel content — which is exactly why the tiled-image render tests (test_detail_tile / test_tiled_imshow, "0 pixels rendered") regressed: the panel drew its overview/detail tile correctly, but the probe sampled the empty overlay on top. Fix: when there is nothing to draw, collapse the overlay to a 0x0 backing store (which also clears any prior drawing) and return, instead of sizing it to the full figure. The overlay grows back to full-figure size the moment an indication / marker exists (covered by the existing TestCalloutRendering and edit-chrome marker suites). Add a regression test pinning that empty overlays stay 0x0 and never shadow panel content.
feat(inset): callout connectors + arbitrary inset placement
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #31 +/- ##
==========================================
+ Coverage 88.97% 90.00% +1.02%
==========================================
Files 38 39 +1
Lines 3511 3801 +290
==========================================
+ Hits 3124 3421 +297
+ Misses 387 380 -7 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
No description provided.