Skip to content
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

[charts] Fix LineChart animation being stuck with initial drawing area value #14553

Merged
merged 6 commits into from
Sep 16, 2024

Conversation

JCQuintas
Copy link
Member

Should improve the behaviour of #13477 when animations are enabled or not 😅

This fix is comprised of two parts

  1. Ensure the animation is run correctly on line chart
  2. Prevent the chart slowly expanding render to happen

While #2 is not an issue of the chart per-se, we can make it work by adding some additional behaviour to the useChartContainerDimensions hook, so it calculates the necessary size it should take before the first render, while trying to prevent an infinite loop in case the layout can't coalesce into a defined size.

@JCQuintas JCQuintas added bug 🐛 Something doesn't work component: charts This is the name of the generic UI component, not the React module! labels Sep 9, 2024
@JCQuintas JCQuintas self-assigned this Sep 9, 2024
@mui-bot
Copy link

mui-bot commented Sep 9, 2024

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

Generated by 🚫 dangerJS against cb7058e

Copy link

codspeed-hq bot commented Sep 9, 2024

CodSpeed Performance Report

Merging #14553 will not alter performance

Comparing JCQuintas:improve-unknown-width-behaviour (cb7058e) with master (dc7b629)

Summary

✅ 3 untouched benchmarks

Comment on lines 37 to 52
// This effect is used to compute the size of the container on the initial render.
// It is not bound to the raf loop to avoid an unwanted "resize" event.
// https://github.com/mui/mui-x/issues/13477#issuecomment-2336634785
useEnhancedEffect(() => {
// computeRun is used to avoid infinite loops.
if (!stateRef.current.initialCompute || stateRef.current.computeRun > 20) {
return;
}

const computedSize = computeSize();
if (computedSize.width !== width || computedSize.height !== height) {
stateRef.current.computeRun += 1;
} else if (stateRef.current.initialCompute) {
stateRef.current.initialCompute = false;
}
}, [width, height, computeSize]);
Copy link
Member

Choose a reason for hiding this comment

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

Could you describe the behavior with for example what is currently happening step by step, and how this PR modify it?

I don't huderstand how adding a useEnhancedEffect could help to prevent inifint loop.

If you want a nice reproduction of an infinit loop there is one here:
#12263 (comment)

Copy link
Member Author

Choose a reason for hiding this comment

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

This useEffect doesn't prevent an infinite loop. Only computeRun > 20 does. It serves as a circuit breaker in case the layout never settles into a defined size, which would mean this useEffect would run indefinitely.

This useEffect only happens before/during the first render, when the page layout is figuring out what size the components need. Hence it is different from the issues in your example, where the loop in on the render side.

To fix the loop in your example, we can try to handle it on the useEnhancedEffect that has the ResizeObserver inside. Though I'm not sure how useful it would be, since my current fix is only for layouts that will definitely settle into a defined width/height, but your example will never settle 😓

Explanation of what it does

The main idea of this useEnhancedEffect is that everything in it will happen before we see anything, since it is running on the layout effect and committing changes to the state, the state will update synchronously.

The state updates until it settles on a size (new size is the same as last one) or it runs more than 20 times (usually it only requires 5-6 runs from what I tested).

Once one of these two conditions are met, the hook stops looping (width/height stop changing), and the first actual react render happens.

Copy link
Member

Choose a reason for hiding this comment

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

I'm ok with the solution. But this will silently delay the painting without warning the developers about the improvement they could bring. We should add at least a warning if computeRun > 2

We can not do computeRun > 0 because the react strict mode trigger twice the useLayoutEffect. Si the ref is updated twice before the state get updated.

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 had added a warning at first but thought it could be unnecessary and removed it 😅

Will add it back

Copy link
Member Author

Choose a reason for hiding this comment

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

Hum, I actually removed it because it is an actual valid behaviour that wouldn't require a warning. 🤔

Like, if the user uses this inside a grid, this will always warn, even though a grid's width would eventually settle in the screen's width.

Copy link
Member

Choose a reason for hiding this comment

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

it is an actual valid behaviour

I disagree on this point. For me the valid behavior is "The container has a defined size". This PR is adding a nice fallback

I tried with Recharts and they have the same behavior as us currently.

Capture.video.du.2024-09-16.10-02-01.mp4

From my point of view it would be bad to have such loop without informing user since it silently fix something the developers could potentially do better.

In addition to a warning, it could be a warning plus a prop to silence the warning. Or no warning and a prop to activate the feature

Copy link
Member Author

@JCQuintas JCQuintas Sep 16, 2024

Choose a reason for hiding this comment

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

As "valid behaviour" I mean it is something the browser does on its own. Not that our behaviour is valid 😅

I would agree with a prop for this though.

Something like:

 /**
  * The chart will try to wait for the parent container to resolve its size
  * before it renders for the first time.
  * 
  * This can be useful in some scenarios where the chart appear to grow after
  * the first render, like when used inside a grid.
  */
  resolveSizeBeforeRender

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.

Good for me. We will see if we get issues about this. In such case, adding a paragraph about this prop in the docs could be the next step

@JCQuintas JCQuintas enabled auto-merge (squash) September 16, 2024 15:33
@JCQuintas JCQuintas merged commit a8e757d into mui:master Sep 16, 2024
17 checks passed
@JCQuintas JCQuintas deleted the improve-unknown-width-behaviour branch September 16, 2024 15:47
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug 🐛 Something doesn't work component: charts This is the name of the generic UI component, not the React module!
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants