This is an automated email from the ASF dual-hosted git repository.
sarutak pushed a commit to branch branch-4.x
in repository https://gitbox.apache.org/repos/asf/spark.git
The following commit(s) were added to refs/heads/branch-4.x by this push:
new 2f6b488c8961 [SPARK-57631][WEBUI] Require modifier key for wheel-zoom
on SQL plan DAG to restore page scrolling
2f6b488c8961 is described below
commit 2f6b488c896176d8f580006060264c742eaffad2
Author: Kousuke Saruta <[email protected]>
AuthorDate: Sat Jun 27 20:41:45 2026 +0900
[SPARK-57631][WEBUI] Require modifier key for wheel-zoom on SQL plan DAG to
restore page scrolling
### What changes were proposed in this pull request?
Require Ctrl (or Cmd on macOS, Super on Linux) + scroll wheel to zoom the
SQL plan DAG on the `/SQL/execution` page. Unmodified scroll gestures now pass
through for normal page scrolling.
Additionally, a transient overlay hint ("⌘ + scroll to zoom" on Mac, "Super
+ scroll to zoom" ,"Ctrl + scroll to zoom" otherwise) is shown when the user
scrolls without the modifier key, following the same UX pattern as Google Maps.
### Why are the changes needed?
The DAG visualization occupies a 70vh viewport and captures all wheel
events for zoom via d3-zoom. This makes it nearly impossible to scroll past the
DAG without moving the cursor to the narrow margins on either side of the graph
area. The problem is especially pronounced on pages with additional content
below the DAG (e.g., when a node detail panel is not open).
### How does this fix work?
1. Added a `wheel` event type check in the `d3.zoom().filter()` to reject
unmodified wheel events.
2. After `svg.call(planVizZoom)`, the d3-zoom wheel listener (registered
with `{passive: false}`) is detached and replaced with a custom wrapper that
only forwards Ctrl/Cmd+wheel to the original d3 handler. This ensures the
browser's native scrolling is never blocked by d3's non-passive listener
registration.
3. A CSS-animated hint overlay is shown on unmodified scroll to inform
users how to zoom.
Notes:
- Trackpad pinch-to-zoom still works because browsers synthesize pinch
gestures as `wheel` events with `ctrlKey: true`.
- Drag-to-pan behavior is unchanged.
- The `+`/`-` toolbar buttons and keyboard shortcuts continue to work as
before.
### Does this PR introduce _any_ user-facing change?
Yes. Wheel-zoom on the SQL plan DAG now requires holding Ctrl (Cmd on Mac).
Plain scroll gestures scroll the page. A hint overlay guides users to the new
behavior.
### How was this patch tested?
Manual testing with Chrome and Firefox on macOS and Ubuntu, and Safari on
macOS:
- Verified unmodified scroll scrolls the page past the DAG
- Verified Ctrl+scroll zooms the DAG
- Verified trackpad pinch zooms the DAG
- Verified drag-to-pan works as before
- Verified Hint overlay appears for 1.0s then enters a 3s cooldown before
it can reappear
- Verified toolbar +/- buttons and keyboard shortcuts still work
https://github.com/user-attachments/assets/f2b856c5-9cb4-4128-bd60-df5990af47bb
### Was this patch authored or co-authored using generative AI tooling?
Kiro CLI / Claude
Closes #56694 from sarutak/fix-sql-dag-zoom-scroll-ux.
Authored-by: Kousuke Saruta <[email protected]>
Signed-off-by: Kousuke Saruta <[email protected]>
(cherry picked from commit 9831817ab9dd8185d9fb0a9741b7623ae9869116)
Signed-off-by: Kousuke Saruta <[email protected]>
---
.../sql/execution/ui/static/spark-sql-viz.css | 20 +++++++++
.../spark/sql/execution/ui/static/spark-sql-viz.js | 48 ++++++++++++++++++++++
2 files changed, 68 insertions(+)
diff --git
a/sql/core/src/main/resources/org/apache/spark/sql/execution/ui/static/spark-sql-viz.css
b/sql/core/src/main/resources/org/apache/spark/sql/execution/ui/static/spark-sql-viz.css
index 738e70fdd127..7ca3ba9275da 100644
---
a/sql/core/src/main/resources/org/apache/spark/sql/execution/ui/static/spark-sql-viz.css
+++
b/sql/core/src/main/resources/org/apache/spark/sql/execution/ui/static/spark-sql-viz.css
@@ -243,3 +243,23 @@ svg g.cluster.search-dimmed {
.sql-cell-details[open] > summary {
margin-bottom: 0.25rem;
}
+
+/* Zoom hint overlay */
+.plan-viz-zoom-hint {
+ position: absolute;
+ top: 50%;
+ left: 50%;
+ transform: translate(-50%, -50%);
+ background: rgba(0, 0, 0, 0.7);
+ color: #fff;
+ padding: 8px 16px;
+ border-radius: 6px;
+ font-size: 14px;
+ pointer-events: none;
+ opacity: 0;
+ transition: opacity 0.2s;
+ z-index: 10;
+}
+.plan-viz-zoom-hint.visible {
+ opacity: 1;
+}
\ No newline at end of file
diff --git
a/sql/core/src/main/resources/org/apache/spark/sql/execution/ui/static/spark-sql-viz.js
b/sql/core/src/main/resources/org/apache/spark/sql/execution/ui/static/spark-sql-viz.js
index 81d4562cbb35..7c1391c9d2b4 100644
---
a/sql/core/src/main/resources/org/apache/spark/sql/execution/ui/static/spark-sql-viz.js
+++
b/sql/core/src/main/resources/org/apache/spark/sql/execution/ui/static/spark-sql-viz.js
@@ -765,6 +765,54 @@ function setupZoomAndPan(svg, zoomLayer) {
// provides the natural fit, and the zoom-layer has no transform, so no
// explicit transform is required here.
updateZoomLevelLabel(1);
+
+ // Remove d3-zoom's wheel listener (which is registered as non-passive and
+ // can block page scrolling) and replace it with a gated wrapper that only
+ // forwards Ctrl/Cmd+wheel to d3, letting unmodified scrolls pass through.
+ // "wheel.zoom" is d3-zoom's internal event namespace (stable since d3 v4).
+ // The null-guard below protects against future d3 changes.
+ var d3WheelHandler = svg.on("wheel.zoom");
+ svg.on("wheel.zoom", null);
+ svgNode.addEventListener("wheel", function (e) {
+ if (e.ctrlKey || e.metaKey) {
+ // Prevent the browser's native page zoom so only the DAG zooms.
+ e.preventDefault();
+ // Forward to d3-zoom's original handler for zoom behavior.
+ // Note: trackpad pinch gestures fire as wheel events with ctrlKey=true,
+ // so pinch-to-zoom still works as expected.
+ if (d3WheelHandler) d3WheelHandler.call(this, e);
+ } else {
+ // Let the event propagate naturally for page scrolling and show hint
+ showZoomHint();
+ }
+ }, {passive: false});
+}
+
+var zoomHintTimeout;
+var zoomHintCooldown = false;
+function showZoomHint() {
+ if (zoomHintCooldown) return;
+ var container = document.getElementById("plan-viz-graph");
+ if (!container) return;
+ var hint = document.getElementById("plan-viz-zoom-hint");
+ if (!hint) {
+ hint = document.createElement("div");
+ hint.id = "plan-viz-zoom-hint";
+ hint.className = "plan-viz-zoom-hint";
+ var platform = navigator.platform || navigator.userAgent;
+ var isMac = /Mac|iPhone|iPad/.test(platform);
+ var isLinux = /Linux/.test(platform);
+ var modKey = isMac ? "\u2318" : isLinux ? "Super" : "Ctrl";
+ hint.textContent = modKey + " + scroll to zoom";
+ container.appendChild(hint);
+ }
+ hint.classList.add("visible");
+ zoomHintCooldown = true;
+ clearTimeout(zoomHintTimeout);
+ zoomHintTimeout = setTimeout(function () {
+ hint.classList.remove("visible");
+ setTimeout(function () { zoomHintCooldown = false; }, 3000);
+ }, 1000);
}
function updateZoomLevelLabel(scale) {
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]