feat: show several legend boxes at once - #1816
Conversation
Every legend was drawn straight into the #legend layer group, so there was only ever one box, and each editor's toggle asked "is anything in #legend?" before deciding to hide it. Turning on the Zones legend while the States one was up therefore cleared States and never drew Zones - one click, no boxes. Each legend now lives in its own <g data-legend="..."> inside the layer, keyed by its title, so the five sources (States, Cultures, Religions, Biomes, Zones) can be shown together - useful when exporting the map as an image. A new box is placed against the shown ones on whichever side has room, and is clamped to stay on the canvas. Dragging, clicking to hide and the editor buttons all act on a single box. Per-box positions live in styles.legend.options.positions, keyed by the box title; options.x/y stays the anchor a box gets when it has no stored spot. The legend rect and title moved from #legendBox/#legendLabel ids to classes, since ids can no longer be unique, and redrawLegend adopts the flat single box of maps saved before this into a named group. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EMcp34mUdSim2ALrFohRMr
✅ Deploy Preview for afmg ready!
To edit notification comments on pull requests, go to your Netlify project configuration. |
|
Nice work, this is a clean take on #1815 and the container-plus-named-groups shape is the right call. I pulled the branch (4a79a95 on c222645) and went through it properly. Most of what I went looking for turned out to be already handled, so first the good news:
One thing I would want fixed before merge, then a few small ones. 1. Switching style presets walks the stack off the anchor. 2. 3. Docs do not quite match the code ( 4. Latent flake in the e2e ( On the two decisions you flagged, both seem reasonable to me, with one consequence worth naming out loud: because Also spotted while I was in there, and definitely not yours: a legend drawn from empty data (Zones editor with everything filtered out) still round-trips a bogus Push fixes as new commits on the branch and I will take another pass. |
|
|
Where a legend box sits is closer to a dialog's position than to the map's style, so it moves out of styles.legend.options into localStorage, keyed by the box title. styles-schema.ts and default-styles.json go back to master. Two things fall out of the move. Applying a style preset no longer resets the positions, so the stack cannot march up the canvas as boxes are placed against each other's stale transforms. And a saved custom preset no longer bakes one map's box coordinates in to replay them on every other map. Placement now derives its rectangles from the remembered positions instead of reading transforms back off the dom, which still holds the previous pass's layout during a redraw, and each candidate spot is checked for overlap rather than assumed clear. options.x/y stays in the style as the anchor a box falls back to, so a single legend behaves exactly as before. The trade-off is that box positions no longer travel with the .map file: a shared map auto-places its boxes. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EMcp34mUdSim2ALrFohRMr
The wiki said a new box goes above the shown ones or into a new column when the stack no longer fits, but placement tries all four sides, so a legend anchored near the top of the canvas gets the next box below it. Also note that where a box is dragged is remembered by the browser, not by the map. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EMcp34mUdSim2ALrFohRMr
The item and title texts are siblings of the background rect and paint over it, and svg text hit-tests on the glyphs, so clicking the rect's centre could land on a glyph that is not its descendant and fail the actionability check. Whether it did depended on how many entries the seed produced. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EMcp34mUdSim2ALrFohRMr
v1.152.0 removed public/main.js (the legend click handler now lives with the renderer and load.ts), sized the legend from the viewport module, dropped the window.* bridges and replaced the e2e map-ready flag with tests/e2e/wait-for-map. Resolution: draw-legend.ts keeps the multi-box body on top of master's viewport and tip imports, wires the layer click to onLegendClick inside drawLegend, and uses viewport.width/height throughout; the unit test sets the viewport with setViewportSize; the e2e spec waits with waitForMap; assets restamped.
Box positions are browser state, the same as where a dialog was dragged, so
they go where that lives: a dedicated "legend" key on dialogState holding the
name -> {x, y, dragged} record. The legendPositions API is unchanged, the
private fmg-legend-positions key and its load/save code are gone, and Clear
cache wipes the positions with the rest of the dialog state.
|
Master moved under this with v1.152.0:
On the result: tsc clean, 951 unit tests, Biome pinned and latest clean, both legend e2e tests green, plus a headless pass (two boxes stacked, preset switch keeps the spots, a drag flags the box and survives a page reload, save → reload restores the boxes, click hides one box, a 1.89 map's single legend is adopted and a second box stacks above it). Your three follow-ups cover everything from my first pass. One optional note: auto-placed spots are remembered across maps until the box is hidden, so on the next map a box goes where it was rather than against what is shown, and a taller States box can end up under a remembered Zones slot. Releasing the non-dragged entries when a map is generated or loaded would keep placement per map and still honour drags. Happy to leave that to you or to a follow-up. |
barrulus
left a comment
There was a problem hiding this comment.
I am fine with this code. I made some small changes at the end because of the changes that happened in 1.152.0
Description
Fixes #1815
Each of the States, Cultures, Religions, Biomes and Zones editors has a "Toggle Legend box" button, but there is only one legend box, and every button checks whether any legend is on the map before deciding to hide it. So with a legend already up, every button reads as "turn it off" — whichever editor it belongs to.
Turning on the Zones legend while the States one is showing therefore clears States and never draws Zones, leaving the map with no legend at all.
The bug
Every editor's toggle asked the same question:
#legendwas the box itself, so "is anything in this group?" was the only test available. One click destroys a legend you had and puts nothing in its place.The change
#legendbecomes a container rather than the box. Each legend lives in its own group inside it, keyed by its title:The layer keeps the font, stroke and colour attributes and the boxes inherit them, so all boxes still share the single style from Style → Legend and that editor needed almost no changes.
Each editor now toggles only its own box:
Dragging and click-to-hide resolve the box under the cursor, so they act on one box too.
A new box is placed against the boxes already shown, taking the first side with room — above, below, left, right — aligned to the edge of the box it is placed against, and clamped so it never lands partly off the canvas. Per-box positions live in
styles.legend.options.positions, keyed by the box title; the existingoptions.x/ystays as the anchor a box gets when it has no stored position, so style presets still control where legends land.Compatibility
#legend.redrawLegenddetects that shape and rehouses it into a named group on load, at the position the map already had. Detection is exact rather than version-gated, so it needs noauto-update.tsentry.positionsis a new key onlegend.optionswith a schema default, so old.mapfiles, the shipped style presets and user-saved custom presets all parse unchanged and without warnings.#legendBox/#legendLabelids to classes, since ids can no longer be unique.styles-legacy.tsstill maps#legendBoxfor harvesting pre-1.150 maps, which have exactly one legend by definition.Screenshots
All five legends shown at once, auto-placed without overlapping:

States and Zones together — the pair from #1815:

Testing
Manual, in the browser:
.map→ reload with three boxes, one dragged to a custom spot: all three return in placeManual testing on a saved map with the legend dragged to the top-left also caught a placement bug the automated tests missed: auto-placement originally searched only up and left, so with no room in either direction a second box landed exactly on top of the first. It now searches all four sides and clamps to the canvas, with two regression tests covering it.
Automated: 12 unit tests in
draw-legend.test.tsand 2 e2e tests inlegend-boxes.spec.ts, plus the existing suites.Notes for review
Two decisions I'd happily change if you'd shape them differently:
data-x/data-yattrs, and there's a test asserting the legend positions from the store rather than from those attrs.