Skip to content

[charts] Fix wheel zoom not centering on cursor position#22811

Merged
JCQuintas merged 8 commits into
mui:masterfrom
JCQuintas:fix/wheel-zoom-cursor-position
Jun 15, 2026
Merged

[charts] Fix wheel zoom not centering on cursor position#22811
JCQuintas merged 8 commits into
mui:masterfrom
JCQuintas:fix/wheel-zoom-cursor-position

Conversation

@JCQuintas

Copy link
Copy Markdown
Member

Problem

Wheel zoom no longer centers on the cursor position — it behaves as if zooming around a fixed/incorrect anchor.

Cause

In useWheelGesture, the throttled handler was created as:

const rafThrottledOnWheel = rafThrottle(onWheelRef.current);

rafThrottle captures the onWheelRef.current value at effect-mount time, i.e. the first-render onWheel closure. That closure holds the first-render drawingArea (zero/default before layout has measured) and optionsLookup, and is never refreshed. The cursor-to-anchor ratio (point.x - left) / width is then computed from stale dimensions, so the zoom ignores the cursor.

The onWheelRef indirection exists precisely to avoid this staleness, but passing .current defeated it.

Fix

Wrap the call in an indirection so the throttled function always invokes the latest closure — matching the existing usePinchGesture, useDragGesture, and useDragOnPressGesture hooks, which already read .current inside the throttled callback.

Regression

Introduced in #22708 (decouple interaction hook from the cartesian zoom). Before that refactor the throttled callback was rebuilt each effect run with fresh drawingArea deps, so cursor anchoring worked.

`rafThrottle(onWheelRef.current)` captured the first-render `onWheel`
closure, locking in the initial (pre-layout, zero) `drawingArea` and
`optionsLookup`. The cursor-to-anchor ratio was then computed from stale
dimensions, so wheel zoom ignored the cursor position.

Wrap the call in an indirection so the throttled function always invokes
the latest closure, matching the pinch and drag gesture hooks.

Regression from mui#22708.
@JCQuintas
JCQuintas requested a review from alexfauquette as a code owner June 15, 2026 11:02
@JCQuintas JCQuintas self-assigned this Jun 15, 2026
@JCQuintas JCQuintas added scope: charts Changes related to the charts. type: regression A bug, but worse, it used to behave as expected. labels Jun 15, 2026
@code-infra-dashboard

code-infra-dashboard Bot commented Jun 15, 2026

Copy link
Copy Markdown

Deploy preview

https://deploy-preview-22811--material-ui-x.netlify.app/

Bundle size

Bundle Parsed size Gzip size
@mui/x-data-grid 0B(0.00%) 0B(0.00%)
@mui/x-data-grid-pro 0B(0.00%) 0B(0.00%)
@mui/x-data-grid-premium 0B(0.00%) 0B(0.00%)
@mui/x-charts 0B(0.00%) 0B(0.00%)
@mui/x-charts-pro 🔺+12B(0.00%) 🔺+2B(0.00%)
@mui/x-charts-premium 🔺+12B(0.00%) 🔺+3B(0.00%)
@mui/x-date-pickers 0B(0.00%) 0B(0.00%)
@mui/x-date-pickers-pro 0B(0.00%) 0B(0.00%)
@mui/x-tree-view 0B(0.00%) 0B(0.00%)
@mui/x-tree-view-pro 0B(0.00%) 0B(0.00%)
@mui/x-license 0B(0.00%) 0B(0.00%)

Details of bundle changes

Performance

Total duration: 1,889.13 ms -88.24 ms(-4.5%) | Renders: 67 (+0)

Test Duration Renders
RadialBarChart stacked with multiple series 26.37 ms 🔺+9.68 ms(+58.0%) 2 (+0)
SankeyChart with big data amount 12.37 ms 🔺+3.48 ms(+39.2%) 2 (+0)
HeatmapPremium: 100x100 grid (webgl renderer) 46.39 ms +5.52 ms(+13.5%) 5 (+0)

23 tests within noise — details

Metric alarms

Test Metric Change
RadialBarChart stacked with multiple series bench:paint 🔺 +17.84 ms
SankeyChart with big data amount bench:paint 🔺 +5.48 ms
HeatmapPremium: 100x100 grid (webgl renderer) bench:paint 🔺 +9.33 ms

Check out the code infra dashboard for more information about this PR.

Renders a responsive ScatterChartPro (no explicit width/height) so the
drawing area is empty on first render and only measured afterwards,
reproducing the stale-closure bug. Zooming near the left edge keeps the
leftmost tick, near the right edge keeps the rightmost; a stale drawing
area anchors at the same place regardless of cursor.
@JCQuintas
JCQuintas enabled auto-merge (squash) June 15, 2026 12:45
Comment on lines +117 to +125
// Regression test for https://github.com/mui/mui-x/pull/22811
// Wheel zoom must anchor on the cursor position. With a responsive chart the
// drawing area is empty on the first render and only gets its real size once
// the container is measured. The wheel handler must read that up-to-date
// drawing area; using a stale (empty) one makes the zoom ignore the cursor.
// Zooming near the left edge keeps the leftmost tick visible, while zooming
// near the right edge keeps the rightmost one.
// `width`/`height` are intentionally omitted so the chart sizes itself from
// the wrapper, reproducing the empty-then-measured drawing area.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Might not need that many comment

@JCQuintas
JCQuintas disabled auto-merge June 15, 2026 13:39
Comment thread packages/x-charts-pro/src/ScatterChartPro/ScatterChartPro.zoom.test.tsx Outdated
@JCQuintas
JCQuintas enabled auto-merge (squash) June 15, 2026 13:43
@JCQuintas
JCQuintas disabled auto-merge June 15, 2026 14:54
@JCQuintas
JCQuintas merged commit 256f05d into mui:master Jun 15, 2026
21 checks passed
@JCQuintas
JCQuintas deleted the fix/wheel-zoom-cursor-position branch June 15, 2026 15:15
mbrookes pushed a commit to mbrookes/mui-x that referenced this pull request Jun 27, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

scope: charts Changes related to the charts. type: regression A bug, but worse, it used to behave as expected.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants