-
-
Notifications
You must be signed in to change notification settings - Fork 1.7k
[charts-pro] Fix zoom pan issue when controlled #20163
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
| accumulatedChange.current.x += event.detail.deltaX; | ||
| accumulatedChange.current.y += event.detail.deltaY; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We store the accumulated changes outside the raf throttle
| const x = accumulatedChange.current.x; | ||
| const y = accumulatedChange.current.y; | ||
| accumulatedChange.current.x = 0; | ||
| accumulatedChange.current.y = 0; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we consume the delta changes when the throttle runs
|
Deploy preview: https://deploy-preview-20163--material-ui-x.netlify.app/ Bundle size report
|
| onZoomChange?.(newZoomData); | ||
| store.set('zoom', { | ||
| ...store.state.zoom, | ||
| isInteracting: true, | ||
| }); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we now set isInteracting: true when we send the new zoom to the onZoomChange, this would lead to small de-sync in data over time due to animations turning on/off
| import { expect } from 'vitest'; | ||
| import { rafThrottle } from './rafThrottle'; | ||
|
|
||
| describe('rafThrottle', () => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
extra tests just in case 🤷
CodSpeed Performance ReportMerging #20163 will not alter performanceComparing Summary
Footnotes
|
|
This pull request has conflicts, please resolve those before we can evaluate the pull request. |
| if (!(event.detail.target as SVGElement)?.closest('[data-charts-zoom-slider]')) { | ||
| startRef.current = store.state.zoom.zoomData; | ||
| isInteracting.current = true; | ||
| accumulatedChange.current = { x: 0, y: 0 }; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Couldn't we lose some updates?
Since rafThrottle will delay the reading of accumulatedChange, if another handlePanStart is called before the callback of throttledCallback fires, the callback will fire with accumulatedChange.current = { x: 0, y: 0 }.
To fix it, I think we need to pass accumulatedChange.current to the throttledCallback so that it retains the reference to the old value. (It works because we're updating accumulatedChange.current by mutating x and y. If we were updating the object directly (accumulatedChange.current = { x, y } it wouldn't work.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Couldn't we lose some updates?
In a real environment it would be very unlikely to loose any update at all.
I've changed it to initialize/change inside the hook.
| const optionsLookup = useSelector(store, selectorChartZoomOptionsLookup); | ||
| const startRef = React.useRef<readonly ZoomData[]>(null); | ||
| const isInteracting = React.useRef<boolean>(false); | ||
| const accumulatedChange = React.useRef<{ x: number; y: number }>({ x: 0, y: 0 }); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think these needs to be refs. Since they're only used inside the useEffect we can create const variable with interior mutability to achieve the same effect.
Basically, we create these variables inside the useEffect similar to a ref: const accumulatedChange = { current: { x: 0, y: 0 }}.
It would also allow us to unit test this logic, which I think we should because it's prone to errors as explained here.
| const x = accumulatedChange.x; | ||
| const y = accumulatedChange.y; | ||
| accumulatedChange.x = 0; | ||
| accumulatedChange.y = 0; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this works, but I'm not sure it's very logical.
IMO, it would be make more sense for throttledCallback to accept the accumulatedChange object and for handlePanStart to create a new { x: 0, y: 0} object when it's fired.
We don't need to reset values inside the throttled function.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The throttle would still fail to always have the correct ref, since older values are dropped.
current:
// - start
// - move, update accumulate + scheduleThrottledFn
// - move, update accumulate + dropOldThrottledFn + scheduleThrottledFn
// - start (values keep accumulating)
// - move, update accumulate + dropOldThrottledFn + scheduleThrottledFn
// - run scheduled fn + reset accumulateyour suggestion:
// - start, new accumulate (ref1)
// - move, update accumulate + scheduleThrottledFn(ref1)
// - move, update accumulate + dropOldThrottledFn(ref1) + scheduleThrottledFn(ref1)
// - start, new accumulate (ref2)
// \/ (ref1) could be completely discarded
// - move, update accumulate + dropOldThrottledFn(ref1) + scheduleThrottledFn(ref2)
// - run scheduled fn(ref2)There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You're right. Your solution looks good, then 👍
Main goal is to remove this line
startRef.current = store.state.zoom.zoomData;which prevented other interactions to run alongside the pan interaction, since the pan interaction would always "reset" any other changes.This is mostly an internal change
You can test this in the http://localhost:3001/x/react-charts/zoom-and-pan/#zoom-synchronization
by reverting the changes in the file:
packages/x-charts-pro/src/internals/plugins/useChartProZoom/useChartProZoom.ts