Make zooming the Image Map cheap on large galleries - #301
Merged
Merged
Conversation
The base points were one scattergl trace carrying per-point colour, opacity and symbol arrays, and plotly reprocesses all of them on every relayout — which a zoom is, once per frame. Splitting them into one trace per distinct appearance (bounded by the palette at 32) makes every marker property scalar: a zoom step drops roughly 14x at 170k points and 20x at 300k, the scene builds about twice as fast, and the traces retain half the heap. Wheel and pinch zooms now coalesce into the next animation frame. A wheel emits far faster than a frame and each event applied its own relayout, so they queued behind the cursor. Composing the factors lands on a bit-identical range, so the gesture ends where it always did, in one paint instead of twelve. Two costs, both measured and accepted. Plotly builds a hit-testing kd-tree only for traces of 100k points or more, so one 170k trace had one and none of these do: hover goes from ~1.5ms to ~7ms at 170k and ~11ms at 300k. And below roughly 5k points the per-trace overhead makes this a small net loss (~2.5ms a frame at 200 points), taken rather than switched on a threshold, because making draw order depend on gallery size is the worse bargain. Appearance is not quite unchanged: points paint grouped rather than in gallery order, so where two overlap the one on top can differ, and markers are translucent. Noise now paints first, which is the deliberate part. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
lstein
requested review from
JPPhoto,
Pfannkuchensack and
blessedcoolant
as code owners
September 21, 2026 02:26
7 tasks
# Conflicts: # invokeai/frontend/webv2/src/workbench/image-map/imageMap.test.ts # invokeai/frontend/webv2/src/workbench/image-map/imageMapTraces.ts
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.
Summary
Zooming the Image Map was slow on a large gallery. The cause was not the point count — it was how the points were styled.
The base points were a single
scattergltrace carrying per-pointcolor,opacityandsymbolarrays. Plotly reprocesses all of them on every relayout, and a zoom is a relayout per frame. On top of that, every wheel event applied its own relayout, and a wheel emits far faster than a frame, so relayouts queued behind the cursor.Measured across four point distributions (even clusters, power-law with a long tail, all-noise, all 32 palette slots occupied):
Absolute numbers are omitted deliberately: two independent harnesses measuring different windows of
Plotly.relayout(synchronous body vs awaited redraw) differed by ~12x in scale while agreeing on the ratio, and everything was measured under SwiftShader. The ratio is the claim.Related Issues / Discussions
Reported alongside the clustering work in #300, which is independent of this and can land in either order.
QA Instructions
Manual: open the Image Map on a large gallery and wheel-zoom. Also worth a pass with a cluster selected (click a cluster, then zoom) — see the limitation below.
Verified in the running app against the mock backend: the live plot reports 6 base traces plus the two overlays, 1008 points, every base trace scalar-styled. Screenshotting the same view before and after and diffing the map region gave 0 pixels differing above 4/255 — but see the appearance note, because that fixture happens not to contain the case that does change.
Review
Three independent read-only reviews (correctness; architecture/performance/operational safety; test value/product quality), each reproducing the measurements in its own harness. Material findings, all resolved:
int, but the client does not validate and the old code still drew such points. Guarded.Compatibility / Rollout
No API, schema, persisted-state or generated-artifact changes. Byte budgets pass without a rebaseline: everything touched sits behind the lazily-imported plot chunk, which no baseline entry graph covers.
Known limitation, not addressed here:
buildHighlightedPointsTracestill carries per-point arrays and sits on the same zoom path, so selecting a cluster and then zooming gives back roughly a third of the win.MAX_CLUSTER_SELECTION = 5000bounds it. The same treatment would apply, but the highlight is restyled in place by index, so splitting it is a larger change than it looks.Checklist
What's Newcopy (if doing a release after this PR)🤖 Generated with Claude Code