Skip to content

[charts-pro] Simplify zoom testing #17525

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

Merged
merged 5 commits into from
Apr 28, 2025
Merged

Conversation

JCQuintas
Copy link
Member

  • Fixes a bug where the selector wouldn't be memoized
  • Make zoom tests easier to reason about

@JCQuintas JCQuintas added type: bug A bug or unintended behavior in the components. scope: charts Changes or issues related to the charts product labels Apr 23, 2025
@JCQuintas JCQuintas self-assigned this Apr 23, 2025
@mui-bot
Copy link

mui-bot commented Apr 23, 2025

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

Generated by 🚫 dangerJS against 0abaff7

Copy link

codspeed-hq bot commented Apr 23, 2025

CodSpeed Performance Report

Merging #17525 will not alter performance

Comparing JCQuintas:improve-zoom-tests (0abaff7) with master (135c785)

Summary

✅ 8 untouched benchmarks

@alexfauquette
Copy link
Member

Fixes a bug where the selector wouldn't be memoized

Could you describe the bug you're fixing?

{
memoizeOptions: {
// This selector returns Record<AxisId, DefaultizedZoomOptions> which is a map.
// Whenever the component re-renders, the axis is often a new object, which makes this a new instance.
Copy link
Member

Choose a reason for hiding this comment

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

I don't understand.

If the axis object provided by the user is a new one. Then nearly everything will update.

Especially memorizing that zoom should be between 0.3 and 0.6 does not seem relevant if you're not sure the axis scale stays the same .

If we want to add a memo, it's more on the selectorChartRawXAxis

Copy link
Member Author

Choose a reason for hiding this comment

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

Sorry, I didn't convey the full issue in the message.

The issue is this line above (xLookup, yLookup) => ({ ...xLookup, ...yLookup }),

If anything in the chain updates and requires a re-evaluation of the hook, not a re-render yet. This hook will force a re-render, because {...} is always a new object.

Copy link
Member Author

Choose a reason for hiding this comment

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

And if the result of this selector is used on a useEffect for example, it will always re-trigger it.

Copy link
Member

Choose a reason for hiding this comment

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

I might miss understand something about selectors.

For me if a selector got the same input it does not run computation and return the memoized result.

The xLookup (same for the yLookp) is derived from the state with the following chain of selectors:

state -> selectorChartCartesianAxisState -> selectorChartRawXAxis -> selectorChartXZoomOptionsLookup

So if user do not modify the xAxis the selectorChartRawXAxis should return the same object.
Since xLookup and yLookup are the same the selector should not run the function and just return the prefiouse ref.

Copy link
Member Author

@JCQuintas JCQuintas Apr 24, 2025

Choose a reason for hiding this comment

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

It is a bit confusing for me too, but as far as I can understand, there are two ways to PREVENT an update.

  1. The function args didn't change.
  2. The result itself didn't change.

If one of these is true, a re-render wont trigger. So we can prevent changes in two different ways.

What this does is that it prevents zoom re-render, even if anything up the tree changed, as long as the result of this specific selector doesn't change.

This means that anything that doesn't change the zoomOptions will be ignored for the sake of re-rendering.

When the zoom is controlled, the axis data will change a lot, and it can cause slowdowns. Event though the zoom options are mostly static. It would also create issues in the zoom hooks, as they would have to re-run mid-gesture.

The zoom options are the only "static" configuration that was updating a lot, an unexpectedly. (Because it would always create a new object).

This change makes sure it only updates when it needs to.

Copy link
Member

Choose a reason for hiding this comment

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

When the zoom is controlled, the axis data will change a lot, and it can cause slowdowns. Event though the zoom options are mostly static. It would also create issues in the zoom hooks, as they would have to re-run mid-gesture.

I struggle to reproduce your issue. I'm playing with the https://mui.com/x/react-charts/zoom-and-pan/#zoom-synchronization and this hook only runs once on mount. I can zoom, pan, and reset zoom the console log inside the function is never called as expected.

Could you share an example of the interaction you're fixing?

Copy link
Member Author

Choose a reason for hiding this comment

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

Yes, the user would have to pass onZoomChange={(v) => setZoomData(v)} for the issue to happen.

Copy link
Member Author

Choose a reason for hiding this comment

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

On my pc the issues happen when I interact fast with the chart, while logging/inspect is open.

Copy link
Member Author

Choose a reason for hiding this comment

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

I've remove it from this PR for now

// This selector returns Record<AxisId, DefaultizedZoomOptions> which is a map.
// Whenever the component re-renders, the axis is often a new object, which makes this a new instance.
// We need to use a custom equality check to avoid re-rendering the chart.
resultEqualityCheck: isDeepEqual,
Copy link
Member

Choose a reason for hiding this comment

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

I think we might be able to achieve the same solution with a different approach.

We're usually getting the zoomOptions only to later access a specific axis' options by doing zoomOptions[axis.id]. Would it make sense to create a selector for that directly (e.g., selectorChartAxisZoomOptions)?

That way in most cases we would skip creating this object and the functions would receive the zoom options for the given axis. Plus, we would also avoid using deep equal here.

Copy link
Member Author

Choose a reason for hiding this comment

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

I would rather not change it now as I use it in a different way on another PR, which this PR was extracted from.

But I like the idea and will try to test it there

Copy link
Member

Choose a reason for hiding this comment

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

In this comment I argued for using isDeepEqual because I thought it would be better and under the condition that we don't use it often, but some days later we're already introducing another usage. That's the reason I'm a bit hesitant about this.

If we can find a way to replace this deep equal (either my suggestion or another approach), and if we agree that we'll refactor the deep equal after this PR and its follow-up are merged, then I'm good and we can merge it. Does that sound good?

My fear is that we might introduce performance problems if we overuse deep equals, and I think we should try to avoid them.

Copy link
Member Author

Choose a reason for hiding this comment

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

I've remove it from this PR for now

@JCQuintas JCQuintas changed the title [charts-pro] Fix zoom selector memoization [charts-pro] Simplify zoom testing Apr 28, 2025
Copy link
Member

@alexfauquette alexfauquette left a comment

Choose a reason for hiding this comment

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

The testing improvement is good. We coudl discuss the zoom issue in a dedicated PR 👍

@JCQuintas JCQuintas enabled auto-merge (squash) April 28, 2025 09:00
@JCQuintas JCQuintas merged commit 76e0bfc into mui:master Apr 28, 2025
20 checks passed
@JCQuintas JCQuintas deleted the improve-zoom-tests branch April 28, 2025 09:01
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
scope: charts Changes or issues related to the charts product type: bug A bug or unintended behavior in the components.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants