This is an automated email from the ASF dual-hosted git repository.

sarutak pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/spark.git


The following commit(s) were added to refs/heads/master by this push:
     new 9831817ab9dd [SPARK-57631][WEBUI] Require modifier key for wheel-zoom 
on SQL plan DAG to restore page scrolling
9831817ab9dd is described below

commit 9831817ab9dd8185d9fb0a9741b7623ae9869116
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]>
---
 .../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]

Reply via email to