You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
[ENHANCEMENT] Rework canvas pan/zoom and add on-screen zoom - #922
This reworks the display panel so that it supports panning and zooming not only for regular mouses but also for trackpads and devices without a scroll wheel (like the Apple Magic Mouse I use). The implementation is inspired by how other tools like Grafana handle in-panel movements:
Pan by dragging the left mouse button anywhere on the canvas (middle-button drag still pans too).
Zoom with Ctrl/Cmd + wheel, or a trackpad pinch (which the browser delivers as a Ctrl+wheel). A plain, unmodified wheel no longer zooms — it scrolls the surrounding dashboard page, so a Magic Mouse swipe behaves as expected.
On-screen controls (zoom in / out / fit-to-view / reset) in a small overlay in the bottom-right, as a fallback for other devices or ways to use the canvas.
The editor (edit mode) is unchanged: it keeps middle-button pan and Ctrl/Cmd+wheel zoom, leaving the left button and plain wheel free for node move, edge drag, rubber-band selection, and page scroll.
Two behaviors are preserved explicitly under the new left-button pan and clamped zoom:
Node navigation — a small clickDistance keeps a near-stationary left click from being treated as a pan, so a node's URL still opens.
Fit to view — fitView widens the scale-extent floor for very large graphs so fit-to-view frames the whole graph.
Left-button panning relies on touch-action: none / user-select: none on the panel SVG so the browser's native drag/selection doesn't preempt the pan.
The gesture predicate (shouldStartGesture) and the zoom-limit logic (isAtScaleLimit) are pure, exported, and unit-tested (d3-zoom is ESM-only and awkward to drive through the DOM).
Screenshots
The only visual addition is the zoom-controls overlay (zoom in / out / fit / reset) in the bottom-right of the display panel; the graph rendering itself is unchanged. See this screenshot:
Checklist
Pull request has a descriptive title and context useful to a reviewer.
Pull request title follows the [<catalog_entry>] <commit message> naming convention using one of the
following catalog_entry values: FEATURE, ENHANCEMENT, BUGFIX, BREAKINGCHANGE, DOC,IGNORE.
Give the read-only canvas display panel Grafana-style zoom/pan via a new
`interactionMode` option on `useZoom`. The editor and display panel share this
hook, so the panel had inherited the editor's gesture constraints despite
having no editing gestures to protect, making a plain view unintuitive.
Interaction model (matching Grafana's node graph):
- Pan by dragging the left mouse button (view mode also keeps middle-button
pan); the editor keeps its middle-button-only pan.
- Zoom with ctrl/meta + wheel (or a trackpad pinch, which the browser delivers
as a ctrl wheel). A plain wheel no longer zooms, so a Magic Mouse / two-finger
swipe scrolls the surrounding dashboard page instead of zooming the panel.
- Discoverable on-screen controls (zoom in/out, fit, reset) via a new
`ZoomControls` overlay, since ctrl+wheel is not obvious.
Supporting changes:
- Add `zoomIn`/`zoomOut` (d3-zoom `scaleBy` about the panel center) and
`scale`/`canZoomIn`/`canZoomOut` to `useZoom`, exposed through `ZoomContext`.
- Keep node URL navigation working under left-button pan via `clickDistance`,
and let `fitView` widen the scale-extent floor so fit-to-view frames large
graphs. Extract `shouldStartGesture` and `isAtScaleLimit` as pure, tested
functions (d3-zoom is ESM-only and awkward to drive through the DOM).
Signed-off-by: Philipp Matthes <p.matthes@sap.com>
Co-authored-by: Markus Wieland <markus.wieland@sap.com>
The reason will be displayed to describe this comment to others. Learn more.
🟡 Changes recommended
The zoom controls incorrectly disable zooming out within the effective extent, and the panel blocks native touch scrolling without providing touch panning.
Updates canvas navigation with display-mode panning and visible zoom controls, building on the data-driven graph work while preserving editor gestures.
Changes:
Enables left/middle-button panning in display mode and Ctrl/Cmd+wheel or trackpad-pinch zoom.
Adds zoom-in, zoom-out, fit-to-view, and reset controls with bounded zoom.
Extends the zoom context and test fixtures, and adds gesture-filter and scale-limit tests.
File
Description
canvas/src/test-utils/hookWrapper.tsx
Adds stubs for the expanded zoom context.
canvas/src/hooks/useZoom.ts
Adds interaction modes, zoom bounds, and button actions.
canvas/src/hooks/useZoom.test.ts
Tests gesture filtering and scale-limit detection.
canvas/src/contexts/ZoomContext.tsx
Exposes zoom actions, scale, and availability flags.
- Stop forcing touch-action: none on the display panel SVG. Pan/zoom is
mouse-only (shouldStartGesture rejects touch events), so suppressing
touch-action only broke page scroll for touchscreen users swiping over
the panel without giving them canvas panning in return. Let the browser
keep its default touch handling.
- Track the effective scale extent in React state. fitView can widen the
lower bound to frame a very large graph; the zoom-controls' disabled
flags now derive from that live extent instead of the static constant,
so zoom-out re-enables correctly after zooming in from a fitted scale
below the configured floor. resetPan and remount restore the configured
extent.
- Add unit coverage for the widened-extent and reset behavior.
Signed-off-by: Philipp Matthes <p.matthes@sap.com>
The reason will be displayed to describe this comment to others. Learn more.
🔵 Needs a closer look
At the canvas zoom limits, Ctrl+wheel or trackpad pinch gestures can zoom the entire dashboard page because their default behavior is not prevented.
No open issues 🎉
1 previously missed issue
In code that hasn't changed since last review
Prevent modified-wheel page zoom at scale limits
canvas/src/hooks/useZoom.ts:145
With the new finite scale extent, d3-zoom ignores a fresh wheel gesture directed past a zoom limit without calling preventDefault(). Consequently, starting Ctrl+wheel or a trackpad pinch while already at the minimum or maximum scale can zoom the entire dashboard page instead of remaining confined to the canvas. Cancel accepted modified-wheel events even when the scale cannot change, while continuing to leave plain wheel events untouched.
…limits
d3-zoom only calls preventDefault on a modified (ctrl/meta) wheel that
actually changes the transform. With the finite view-mode scaleExtent, a
ctrl+wheel or trackpad pinch directed past the min/max scale clamps to an
unchanged value and d3-zoom bails before preventing the default, so the
browser zooms the whole dashboard page instead of staying confined to the
canvas.
Attach a non-passive native wheel listener on the panel SVG that cancels
the default for the modified wheels we claim, regardless of whether the
scale changes, while leaving a plain wheel untouched so it keeps scrolling
the surrounding page. The listener is detached via the callback ref's
cleanup. The decision lives in an exported pure predicate
(shouldPreventWheelDefault) with unit coverage, plus integration tests
that dispatch wheel events at the scale limit and after ref teardown.
Signed-off-by: Philipp Matthes <p.matthes@sap.com>
Thanks for the re-review — addressed the "Prevent modified-wheel page zoom at scale limits" finding in 3bc29f9.
You're right: d3-zoom's wheeled only calls preventDefault when a modified wheel actually changes the transform. With the finite view-mode scaleExtent, a ctrl/⌘+wheel or trackpad pinch directed past the min/max clamps to an unchanged scale and d3-zoom hits its t.k === k early-return beforenoevent(event), so the browser's default page zoom fires.
Fix: a non-passive native wheel listener on the panel SVG now cancels the default for the modified wheels we claim, whether or not the scale changes, while a plain wheel is left untouched so it keeps scrolling the surrounding page. The listener is detached in the callback ref's cleanup. The decision is an exported pure predicate (shouldPreventWheelDefault) with unit coverage, plus integration tests that dispatch a ctrl+wheel at the max scale limit (asserting defaultPrevented) and after ref teardown.
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
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.
Description
Follow-up from #891 and perses/perses#4634
This reworks the display panel so that it supports panning and zooming not only for regular mouses but also for trackpads and devices without a scroll wheel (like the Apple Magic Mouse I use). The implementation is inspired by how other tools like Grafana handle in-panel movements:
The editor (
editmode) is unchanged: it keeps middle-button pan and Ctrl/Cmd+wheel zoom, leaving the left button and plain wheel free for node move, edge drag, rubber-band selection, and page scroll.Two behaviors are preserved explicitly under the new left-button pan and clamped zoom:
clickDistancekeeps a near-stationary left click from being treated as a pan, so a node's URL still opens.fitViewwidens the scale-extent floor for very large graphs so fit-to-view frames the whole graph.touch-action: none/user-select: noneon the panel SVG so the browser's native drag/selection doesn't preempt the pan.The gesture predicate (
shouldStartGesture) and the zoom-limit logic (isAtScaleLimit) are pure, exported, and unit-tested (d3-zoom is ESM-only and awkward to drive through the DOM).Screenshots
The only visual addition is the zoom-controls overlay (zoom in / out / fit / reset) in the bottom-right of the display panel; the graph rendering itself is unchanged. See this screenshot:
Checklist
[<catalog_entry>] <commit message>naming convention using one of thefollowing
catalog_entryvalues:FEATURE,ENHANCEMENT,BUGFIX,BREAKINGCHANGE,DOC,IGNORE.UI Changes