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] Remove react spring #17123

Merged
merged 27 commits into from
Apr 1, 2025

Conversation

bernardobelchior
Copy link
Member

@bernardobelchior bernardobelchior commented Mar 25, 2025

Fixes #16281.

Remove react-spring and replace it with CSS transitions or our useAnimate hook.

Changelog

Removes react-spring as a dependency of @mui/x-charts. A consequence of this change is that the props of some slots have been changed because the SpringValue wrapper has been removed. The affected slots and props are:

  • the type of the x, y, width and height props of the bar slot are now number;
  • the type of startAngle, endAngle, innerRadius, outerRadius, arcLabelRadius, cornerRadius and paddingAngle props of pieArcLabel slot are now number.

const time = now() - this.elapsed;

this.timer = timer((elapsed) => this.timerCallback(elapsed), 0, time);
timerFlush();
Copy link
Member Author

Choose a reason for hiding this comment

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

Without timerFlush():

Screen.Recording.2025-03-25.at.08.37.47.mov

With timerFlush():

Screen.Recording.2025-03-25.at.08.38.02.mov

@mui-bot
Copy link

mui-bot commented Mar 25, 2025

@bernardobelchior bernardobelchior force-pushed the remove-react-spring branch 7 times, most recently from 4960d5c to e95e201 Compare March 26, 2025 15:58
Copy link

This pull request has conflicts, please resolve those before we can evaluate the pull request.

@github-actions github-actions bot added the PR: out-of-date The pull request has merge conflicts and can't be merged label Mar 26, 2025
@bernardobelchior bernardobelchior force-pushed the remove-react-spring branch 2 times, most recently from ba4ee01 to b8c3085 Compare March 26, 2025 16:45
@github-actions github-actions bot removed the PR: out-of-date The pull request has merge conflicts and can't be merged label Mar 26, 2025
Comment on lines +160 to 162
xOrigin: xScale(0) ?? 0,
yOrigin: yScale(0) ?? 0,
height: verticalLayout ? maxValueCoord - minValueCoord : barWidth,
Copy link
Member Author

Choose a reason for hiding this comment

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

Not sure how to handle this. Technically, defaulting this to 0 is wrong, but it's also wrong to assert as non-undefined because it can be.

To have proper types, I suppose we'd need to change the return value depending on the layout.

Open to suggestions.

Copy link
Member

Choose a reason for hiding this comment

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

const xOrigin = xScale(0)

if (!xOrigin) throw 💣 

🤷

I think defaulting to 0 is reasonable enough, even though it could create issues, it shouldn't 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.

If we throw our tests will start failing. The thing is that we don't use the value in the cases where it doesn't make sense, so there's no issue. If we throw, we'll break our tests.

yOrigin,
y,
width,
height: -8,
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 feels a bit hacky because it requires knowledge of the implementation details of useAnimateBarLabel.

I wonder if we should expose useAnimate after all (or maybe a higher-level version of it?)

Copy link
Member

Choose a reason for hiding this comment

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

That's weird. Is it just to specify the initial value that you need this height: -8

If yes it might be easier to let user specify their own initial values.

Wy not having also the default initial height adapting to the layout like you do for x and y.

const initialProps = {
  ...,
  height: props.layout === 'vertical' ? 0 : props.height

And if the -8 is here to hide the element, maybe the opacity could be an extra label to animate

Copy link
Member Author

Choose a reason for hiding this comment

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

Is it just to specify the initial value that you need this height: -8

It's not about the initial value, it's about the end value actually. I've since updated this code, but it's still a bit weird.

By default we render the bar labels in the middle of the bar. In this demo I'm moving them to the top of the bar and keeping the animation, so that's why I need this hack.

If yes it might be easier to let user specify their own initial values.

That's another use case, but yeah, I'm considering exporting a slightly higher level version of useAnimate.

I'll try to get a grasp of how something like that would look like.

Copy link
Member

Choose a reason for hiding this comment

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

I got confused because the y-axis goes from top to bottom 🙈

Maybe the solution for now is to not encourage users to create custom animation with those hook.

  • User want to create a custom component because they need to add extra element: They can reuse the hook
  • User want to customize the animation: They pick their own animation library

Copy link
Member Author

Choose a reason for hiding this comment

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

Created a higher-level useAnimateRef hook here.

We might want to expose that one so that users can customize the animations using our "framework".

Copy link
Member

Choose a reason for hiding this comment

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

We might want to expose that one so that users can customize the animations using our "framework".

I agree. My point was more about the timeline. This PR could have a docs with a simpler example. But I struggle to find an interesting one. I thought about a bar label with a rectangle behind it but it does seems as hacky as the current one because the useBarLabelAnimation will only animate the x and y.

It seems that without hack, those hook are only to reproduce the exact same behavior as default components.

So maybe in this PR just documenting that user can use those hook to create their own version. And in another PR we will discuss the DX and

Copy link
Member Author

Choose a reason for hiding this comment

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

So maybe in this PR just documenting that user can use those hook to create their own version. And in another PR we will discuss the DX and

Sure. So you're saying that we should ship this as is and then in a separate PR export a higher-level hook to be customizable by users, right?

Copy link
Member Author

Choose a reason for hiding this comment

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

@bernardobelchior bernardobelchior changed the title WIP: Remove react spring Remove react spring Mar 27, 2025
@bernardobelchior bernardobelchior marked this pull request as ready for review March 27, 2025 12:03
@bernardobelchior bernardobelchior added enhancement This is not a bug, nor a new feature component: charts This is the name of the generic UI component, not the React module! labels Mar 27, 2025
Copy link

codspeed-hq bot commented Mar 27, 2025

CodSpeed Performance Report

Merging #17123 will improve performances by 58.76%

Comparing bernardobelchior:remove-react-spring (2007381) with master (347f7d6)

Summary

⚡ 3 improvements
✅ 5 untouched benchmarks

Benchmarks breakdown

Benchmark BASE HEAD Change
BarChart with big data amount 502.8 ms 354 ms +42.05%
BarChartPro with big data amount 751.1 ms 524.1 ms +43.31%
PieChart with big data amount 131.8 ms 83 ms +58.76%

@bernardobelchior bernardobelchior added this to the 8.0.0 milestone Mar 27, 2025
@bernardobelchior bernardobelchior changed the title Remove react spring [charts] Remove react spring Mar 27, 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.

Looks good. All the comments are more thoughts I got while reading than requests for changes

y={y}
width={width}
height={height}
skipAnimation={skipAnimation ?? false}
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
skipAnimation={skipAnimation ?? false}
skipAnimation={skipAnimation}

Usually false has the same behavior as undefined

Copy link
Member Author

Choose a reason for hiding this comment

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

Yeah, but the problem is that we're now exposing skipAnimation through slots, so it might not be clear to users what undefined means. IMO, we should always return either false or true.

Would it make sense to update useSkipAnimation to reflect that? I think so. If you agree, I'll do that in a separate PR.

Copy link
Member

Choose a reason for hiding this comment

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

Please check, but IIRC we used undefined|true because that was the ReactSpring api, now that we got rid of it, we can have undefined | boolean

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 get that. What do you mean by it was the React Spring API? It seems we are the ones setting the type to true | undefined in line 17 of AnimationProvider.tsx.

Nevertheless, I wanted to know if we could just return boolean from useSkipAnimation, instead of boolean | undefined. Basically, I need to know: what's the difference between undefined and false?

Copy link
Member

Choose a reason for hiding this comment

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

Hum, then I'm not sure why. It could have been different behaviours between false vs undefined.

Returning boolean should be ok

yOrigin,
y,
width,
height: -8,
Copy link
Member

Choose a reason for hiding this comment

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

I got confused because the y-axis goes from top to bottom 🙈

Maybe the solution for now is to not encourage users to create custom animation with those hook.

  • User want to create a custom component because they need to add extra element: They can reuse the hook
  • User want to customize the animation: They pick their own animation library

Comment on lines +35 to +42
x: number;
xOrigin: number;
y: number;
yOrigin: number;
width: number;
height: number;
layout: 'horizontal' | 'vertical';
skipAnimation: boolean;
Copy link
Member

Choose a reason for hiding this comment

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

Maybe a BarCommonProps could help to define whihc porps are needed to draw an element (bar, barLabel, barClipPath)

Copy link
Member Author

@bernardobelchior bernardobelchior Mar 28, 2025

Choose a reason for hiding this comment

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

Initially this looked like a good idea to me, but the comments on each property are slightly different. BarClipPath and BarElement aren't exported, so we don't need to document their properties.

BarProps and BarLabelProps are exported, so we should document those. However, the comments aren't the same for properties with the same name:

BarProps:

 /**
   * The position of the bar in the x-axis.
   */
  x: number;
  /**
   * The position of the bar in the y-axis.
   */
  y: number;

BarLabelProps:

    /**
     * Position in the x-axis of the bar this label belongs to.
     */
    x: number;
    /**
     * Position in the y-axis of the bar this label belongs to.
     */

So, I suppose it makes sense that they're duplicated? What do you think?

Copy link
Member

Choose a reason for hiding this comment

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

I did not thougth about JSDocs. Then I'm ok to keep it as it is
👍

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.

Looks good. All the comments are more thoughts I got while reading than requests for changes

@JCQuintas
Copy link
Member

JCQuintas commented Mar 31, 2025

I thought it had something to do with react updates. Maybe merge master in again? The changes in this PR shouldn't affect the axes, so it could be something outside of this PR

It still happens but I just accepted the changes, the benefits are bigger than small text changes. It might be difference in render since we were probably using react:18 on docs prior to this pr.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
breaking change component: charts This is the name of the generic UI component, not the React module! enhancement This is not a bug, nor a new feature
Projects
None yet
Development

Successfully merging this pull request may close these issues.

[charts] Dependency with react-spring makes it incompatible with react v19
4 participants