chore(release): v0.7.0 — land the plot-key overlays that missed main - #51
Merged
Conversation
A key is the scale bar's sibling: a small picture that floats in screen space over the plot area and does not pan or zoom with the data. It is for a colour legend a colorbar cannot express — an inverse pole figure triangle over an orientation map, a hue wheel over a polarization field, a phase key over a segmentation. Deliberately NOT an inset axes. Figure.add_inset gives a draggable window with a title bar, a theme border and a full canvas stack; Python exposes no way to turn any of that off. That is right when the overlay is a live plot you interact with, and much too heavy when it is a static picture that should read as part of the figure. A key has no chrome unless asked for, no event wiring, and (once the renderer lands) one canvas shared by every key on the panel. Lives on _BasePlot, so every panel type has it — the same picture makes sense over a 2-D map and a 3-D scatter. The picture and the styling travel on separate channels. `keys` carries placement and style on the light view trait; `key_images` carries the data: URLs and is declared in _GEOM_KEYS wherever the panel class has one, so restyling a key — or a hover toggle, once that is wired — never re-transmits the image. Plot1D has no geom trait by design, and needs no special case: `key_images` simply rides its single trait as any other state field does. This is the Python layer only: state, validation and lifecycle. The renderer does not draw keys yet, so add_key currently has no visible effect; the JS follows in the next commit.
The renderer half of add_key. Every key on a panel shares ONE canvas (p.keyCanvas, z 7, built generically in _buildCanvasStack for all three panel kinds — each branch already ends with a positioned wrap). Because that canvas is separate from plotCanvas, an ordinary data redraw does not erase it. Keys are therefore redrawn only when the key state, the panel size, or the hover flag changes, which is what makes `hover_only` cheap enough to hang off mouseenter/mouseleave. Those two listeners are attached in _attachPanelEvents rather than in each per-kind handler: it is the one panel behaviour identical across all four, and it must not depend on a kind remembering to wire it. Keys pin to the panel's IMAGE / plot box — the same four numbers _resizePanelDOM drives the scale bar off — not the letterbox fit-rect, so a key stays put when the data's aspect ratio changes underneath it. hover_only keys are kept OUT of PNG export, and that needed real work rather than the comment I first wrote claiming it fell out for free. The key canvas is persistent: if the pointer happens to be over the panel when exportPNG runs, the key is already painted and composites straight through. Verified by rendering, hovering, and re-exporting — max channel delta was 210 before the fix, 0 after. _drawPanelKeysNoHover re-renders onto a scratch canvas with the flag suppressed, mirroring the existing _drawPanelWidgetsNoHandles precedent. A caption on a card defaults to near-white rather than theme.tickText: the usual card is a translucent dark slab and tickText is near-black under a light theme, so the default put dark text on a dark pill. With no card the label sits on the figure and takes the tick colour as before. FIGURE_ESM.md anchors re-verified against all 9,466 lines.
Reverses the export rule: a saved figure now shows every key. The export renders the panel as though the pointer were over it, so what you save is what you see while reading the plot. Fixing this surfaced a real bug that was not about export at all. Images were decoded lazily from inside the DRAW loop, which only ever visits the keys shown this frame — so a hover_only key that had never been on screen had never begun decoding. The first hover would reveal nothing until some later redraw happened to come along, and an export could not wait for onload at all, so it silently omitted the key. Every DECLARED key now starts decoding, whether or not it is drawn. Second trap, in the same area: a panel whose only keys are hover_only kept a display:none canvas while the pointer was away, and the export path measures against that canvas's bounding rect — which is 0x0 when hidden, so _drawEl dropped the key on the floor. The canvas is now sized and shown whenever any key is declared, and merely left empty when none are currently visible. `declared` and `shown` are now separate filters for exactly this reason. Verified both ways round: with mixed keys and with hover_only keys alone, the cold export (pointer away) and the warm export (pointer over the panel) are byte-identical and both contain the key.
33 tests. The Python half covers the lifecycle and validation; the render
half covers what only the browser can answer.
Notable cases, each pinned to a decision rather than to current output:
* a bare RGBA key draws NO card — alpha 0 stays transparent, which is the
whole reason a triangle or a disc can sit on an image
* keys pin to the AXES BOX, not the letterboxed picture. The panel is made
much wider than its square image so the two differ, and the key must
track imgX. This is the scale bar's rule and the test says so.
* hover_only stays dark until the pointer arrives, and a plain key is
unaffected by hovering
* three export regressions: a hover_only key must appear in an export
taken with the pointer away; cold and hovered exports must be
byte-identical; and a LONE hover_only key must still export — that last
one is the display:none / 0x0-rect trap, which no other key is around to
mask.
Adds a shared `mount_page` fixture to tests/conftest.py. `interact_page`
calls renderFn directly and exposes no mount handle, so it can drive a
pointer but cannot export or reach the panels map; these tests need both at
once. test_export_png.py and test_texture.py predate it and keep their own
local copies.
`labels=` draws text INSIDE the key, positioned in fractions of the key
image so it tracks the picture through a resize. This is what an IPF
triangle actually needs — its corner indices belong on the corners, not in
the caption underneath — and it reuses the existing mini-TeX `_drawTex`, so
`$[1\bar{1}0]$` works as in any other label.
Entries are `(x, y, text)` or a dict adding `size` / `color` / `align`.
`align` matters more than it looks: centring text on a corner hangs half of
it off the edge, where the panel clips it, which is exactly what the first
draft of the example did.
Glyphs are drawn white over a dark halo rather than in a theme colour. A
key's own colours are arbitrary — an IPF triangle runs through the entire
hue circle — so there is no background to key the text off, and any single
flat colour is illegible against part of it.
Adds Examples/PlotTypes/plot_key_overlays.py: an IPF colour key over a
synthetic orientation map (grains coloured by reading the key image itself,
so map and legend agree by construction) and a hue wheel over a vortex
polarization field. Plus the changelog fragment and a docs/api/keys.rst
page.
Tests: 40, up from 33. The label pair is a positive/control pair, and the
control earned its place — it caught the first version of the positive
test, which counted "near-white pixels in the red mask's bounding box" and
so was really counting the page background showing through the disc's
corners. Both now crop to the disc interior and detect glyphs by "strong
green AND blue over a pure-red key", which does not depend on antialiasing.
feat(keys): floating image keys pinned over a panel (IPF, colour wheels)
anyplotlib #49 was merged into feat/3d-colormap-surface at 09:12, but that branch had already merged to main at 06:50 as #48 — so the six keys commits were left on a branch nobody merges again, and v0.6.0 shipped set_texture WITHOUT add_key. GitHub reports #49 as MERGED, which is true and misleading: it merged into its base, and its base was already spent. This brings them onto main unchanged.
0.7.0 is the plot-keys feature that missed the 0.6.0 boat: #49 merged into feat/3d-colormap-surface at 09:12, but that branch had already merged to main at 06:50 as #48, so its six commits were stranded and v0.6.0 shipped set_texture without add_key. Mirrors what the Prepare Release workflow does for bump=minor, stable: version in pyproject.toml and docs/conf.py, towncrier build, switcher.json entry, and the root redirect. NB uv.lock still records version 0.5.0 — it was already stale on main (the workflow never touches it), so it is left alone here rather than mixed into a release commit.
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #51 +/- ##
==========================================
+ Coverage 90.58% 90.77% +0.19%
==========================================
Files 39 40 +1
Lines 4345 4500 +155
==========================================
+ Hits 3936 4085 +149
- Misses 409 415 +6 ☔ 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.
Releases the plot-keys feature that missed the 0.6.0 boat, and explains how it
went missing.
What happened
#49 was stacked on
#48 and merged into
feat/3d-colormap-surface— but that branch had already merged tomain:mainfeat/3d-colormap-surfaceSo #49's six commits landed on a branch nobody merges again. GitHub reports
#49 as MERGED, which is true and misleading — it merged into its base, and
its base was spent. The visible symptom: v0.6.0 shipped
set_texturewithout
add_key, andanyplotlib/keys.pyis absent frommain.Nothing is wrong with the code — it just never got to
main.What this PR does
main, unchanged. No conflicts,no rebase, no cherry-pick — so the history and authorship of feat(keys): floating image keys pinned over a panel (IPF, colour wheels) #49 are
preserved exactly as reviewed.
bump=minor, stable: version inpyproject.tomlanddocs/conf.py,towncrier build(one fragment — the keys feature), theswitcher.jsonentry, and the root redirect.
0.7.0 rather than 0.6.1:
add_keyis a new feature, and 0.6.0 is alreadypublished with a changelog that does not mention it.
Verification
anyplotlib/tests/test_keys— 40 passed, run 4x.add_keyandset_textureboth present.Two notes for the reviewer:
test_keys.py::TestRender::test_a_bare_key_draws_no_cardfailed once onthe very first run in a cold worktree, then passed 4/4. It looks like a
first-browser-launch timing flake rather than a real fault — flagging it
because CI cold-starts every time, so it may resurface there.
uv.lockstill recordsversion = "0.5.0"whilemain'spyproject.tomlsays
0.6.0. That drift predates this PR (the release workflow nevertouches the lockfile), so it is deliberately not fixed here — worth a
separate one-liner.
Why now
SpyDE wants
add_key(..., hover_only=True)for the IPF colour key over anorientation map — the exact case #49 was written for. It currently draws that
key as an entire separate figure pinned by the renderer, which
add_keycollapses to one call. SpyDE 0.3.0 would rather depend on a PyPI release than
carry a git pin, so this unblocks it.