feat(pan-zoom): configurable zoom-out for the double-click toggle - #83
Open
webard wants to merge 1 commit into
Open
feat(pan-zoom): configurable zoom-out for the double-click toggle#83webard wants to merge 1 commit into
webard wants to merge 1 commit into
Conversation
In `zoomOnDoubleClick: 'toggle'`, the second double-click restores the exact viewport the first one left — unless there is nothing to restore, which happens more often than it sounds: the user reached this zoom by wheel or `setViewport()`, or panned after zooming in, which drops the memory by design. That branch went to `minZoom`, hard-coded, and the only way to move it was to raise `minZoom` itself — the same floor the wheel and pinch answer to. `dblClickZoomOutLevel` names that fallback alone; a remembered viewport still wins over it. - `'min'` (default) — `minZoom` about the cursor, exactly as before. - `'fit'` — the viewport that frames every visible node, the one `fitView()` computes. On a canvas people read rather than survey, the gesture means "closer", then "show me all of it", and `minZoom` frames nothing in particular. Falls back to `'min'` when there is nothing to fit, so an empty canvas keeps a live gesture. - a number — a fixed level about the cursor, clamped to [minZoom, maxZoom]. `pan-zoom` knows nothing about nodes, so `'fit'` asks for the viewport through a new `getFitViewport` option, wired in `_initPanZoom` from `getNodesBounds()` + `getViewportForBounds()`. It is a thunk: most double-clicks zoom in, and walking every node's bounds for a branch that will not run is work nobody asked for. The no-headroom guard now measures against whatever the toggle zooms out to rather than always `minZoom`, so a numeric out-level at or above the zoom-in level falls back to d3's stepped handler instead of installing a gesture that would stall on its second half.
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.
What
dblClickZoomOutLevel— where azoomOnDoubleClick: 'toggle'double-click zooms out to when it has no remembered viewport to restore.Why
The toggle's headline behaviour — second double-click restores the exact viewport the first one left — is the right answer whenever there is a viewport to restore. The branch with nothing remembered is reached more often than it sounds: the user got to this zoom by wheel or
setViewport(), or panned after zooming in, which drops the memory by design (rememberedViewport = nullon any user-sourced'start').That branch went to
minZoom, hard-coded. The only lever on it wasminZoomitself — the same floor the wheel and pinch answer to — so "zoom out to something sensible on double-click" and "let the wheel go down to 0.5" could not both be had.Where this came from: a workflow editor at
dblClickZoomLevel: 1. The gesture reads as closer, then show me all of it — and "all of it" is the graph's own extent, not an arbitrary floor that frames nothing in particular.Shape
'min'(default) —minZoomabout the cursor, byte-for-byte the old behaviour.'fit'— the viewportfitView()computes for every visible node. Falls back to'min'when there is nothing to fit (empty canvas, nothing measured yet), so the gesture never goes dead.number— a fixed level about the cursor, clamped to[minZoom, maxZoom].A remembered viewport still wins over all three; the option only names the fallback.
pan-zoomknows nothing about nodes, so'fit'asks for the viewport through a newgetFitViewport?: () => Viewport | nulloption, wired in_initPanZoomfromgetNodesBounds()+getViewportForBounds()— the same pairfitView()uses, with the sameDEFAULT_FIT_PADDING. It is passed toresolveDblClickZoomas a thunk: most double-clicks zoom in, and walking every node's bounds for a branch that will not run is work nobody asked for.The no-headroom guard now measures against whatever the toggle zooms out to rather than always
minZoom, sodblClickZoomLevel: 1.5withdblClickZoomOutLevel: 1.5keeps d3's stepped handler instead of installing a gesture that stalls on its second half — same rule as the existinglevel <= minZoomcase.'fit'is measured at gesture time and can't be checked statically, so it is held to the'min'floor and the runtime fallback covers the rest.Tests
src/core/pan-zoom.test.ts— eight cases on the pureresolveDblClickZoom:'fit'returns the fitted viewport pan and all; the fallback tominZoomwhen there is nothing to fit; the thunk is not called on a zoom-in; a remembered viewport still wins; a numeric level zooms out about the cursor and clamps tominZoom; an out-level with no room below returns the current viewport by reference (the existing "honest no-op" contract); and the default matches the pre-option behaviour exactly.src/core/pan-zoom-dblclick.test.ts— four in the wiring suite, two driving a real DOM double-click through the d3 transition:'fit'settles on the supplied viewport, no measuring while the gesture zooms in, and the attach/skip decision for a numeric out-level.npm run test— 187 files, 3044 tests, all passing. Five of the new cases fail againstdevwithout the source change.Not included
dist/is not rebuilt — source, tests and docs only. No version bump, noCHANGELOG.mdentry, no new dependencies.Independent of #82; both branch off
devand touch different files.