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] Decouple margin and axis-size #16418

Draft
wants to merge 18 commits into
base: master
Choose a base branch
from

Conversation

JCQuintas
Copy link
Member

Testing of #16383 (comment)

@JCQuintas JCQuintas added breaking change 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 Jan 31, 2025
@JCQuintas JCQuintas self-assigned this Jan 31, 2025
@mui-bot
Copy link

mui-bot commented Jan 31, 2025

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

Generated by 🚫 dangerJS against 0daad2b

Copy link

codspeed-hq bot commented Jan 31, 2025

CodSpeed Performance Report

Merging #16418 will not alter performance

Comparing JCQuintas:axis-size-poc (acc67bd) with master (d8f2691)

Summary

✅ 6 untouched benchmarks

@JCQuintas
Copy link
Member Author

JCQuintas commented Feb 3, 2025

@alexfauquette having the config in either the plugin or in the component cause a re-render which slows up the chart.

An alternative could be to get rid of left/right/top/bottomAxis properties, move all the config into the x/yAxis, which will then process everything before they render as far as I'm aware. 🤔

eg:

<LineChart
  xAxis={[
    {
      data: ['A', 'B', 'C', 'D'],
      scaleType: 'point',
      position: 'right',
    },
  ]}
  series={[{ data: [40, 30, 20, 10] }]}
  height={200}
/>

@alexfauquette
Copy link
Member

alexfauquette commented Feb 3, 2025

An alternative could be to get rid of left/right/top/bottomAxis properties, move all the config into the x/yAxis, which will then process everything before they render as far as I'm aware. 🤔

Sounds reasonable. Wwe will need to find a hack to let the selectorChartDrawingArea accept to support this new notion of axis dimensions depending on the existence of the cartesian axis plugging or not.

You would still have the plugin in the following order "dimensions > series > axes" but the dimensions could have a selector that uses xAxis and yAxis config if available. The selectorChartDrawingArea would depend on two selectors selectorChartMargin and selectorChartAxisSpace.

If later we want to adapt the size according the real space an axis takes we will also need to take into account the zoom and the tick we render.

I assume the idea is then to have something like

xAxis={[
    {
      data: ['A', 'B', 'C', 'D'],
      scaleType: 'point',
      position: 'right',
      width: 95 // specify the space taken by the axis.
    },
  ]}

@alexfauquette
Copy link
Member

By the way, the removal of leftAxis, ... can be done in a non breaking way.

We can deprecate the props and keep default the same

@JCQuintas
Copy link
Member Author

@mui/xcharts what do you think about this API? I still have to make height/width count towards the margins, but it was trivial to allow multiple axis on a side in this implementation 😄

Screenshot 2025-02-05 at 17 58 34
import * as React from 'react';
import { BarChart } from '@mui/x-charts/BarChart';

export default function BasicBars() {
  return (
    <BarChart
      xAxis={[
        { scaleType: 'band', data: ['group A', 'group B', 'group C'], position: 'top' },
        { scaleType: 'band', data: ['group 1', 'group 2', 'group 3'], position: 'top' }
      ]}
      series={[{ data: [4, 3, 5] }, { data: [1, 6, 3] }, { data: [2, 5, 6] }]}
      margin={60}
      width={500}
      height={300}
    />
  );
}

@alexfauquette
Copy link
Member

I was effectively thinking of this issue #16387 about multiple axis as a nice example of what this new axis config could fix :)

I'm good with the API, and adding width and height as properties that get defaulted on x/y axes.
I would be against something like { position: 'top', size: { width: 50, height: 150 } } because for x-axis user will only care about the height, and with y-axis only about width.


export interface ChartsAxisProps {
/**
* Indicate which axis to display the top of the charts.
* Can be a string (the id of the axis) or an object `ChartsXAxisProps`.
* @default null
* @deprecated Use `xAxis[].position="top"` instead.
Copy link
Member

Choose a reason for hiding this comment

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

We're suggesting using xAxis, but xAxis isn't a prop of this component. I'm a bit confused by this, so I suspect a user would be too. Where should I set the xAxis prop to maintain the behavior?

Copy link
Member Author

Choose a reason for hiding this comment

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

🤔 true it is not a prop of the ChartsAxis but it is of the XxxChart since it reuses the props.

In this use-case I would assume there is no "single best approach", since the prop is not on this component, but also not on a single external component.

What we can do is provide a url to the migration guide where we can explain with specific examples.

Copy link
Member

Choose a reason for hiding this comment

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

Yeah, I'm not sure what's the best way to proceed here. I suppose a URL to the migration guide is helpful, at least, so unless we can think of any better option, that seems good to me 👍

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 not super concerned by this point. I guess most of the users are using directly the ChartsXAxis/ChartsYAxis and they all now where the xAxis props goes because with series they are the most used

Copy link

github-actions bot commented Feb 7, 2025

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 Feb 7, 2025
@github-actions github-actions bot removed the PR: out-of-date The pull request has merge conflicts and can't be merged label Feb 7, 2025
Comment on lines 9 to 15
export const DEFAULT_AXIS_SIZE = 30;
export const DEFAULT_AXIS_SIZES = {
top: DEFAULT_AXIS_SIZE,
bottom: DEFAULT_AXIS_SIZE,
left: DEFAULT_AXIS_SIZE,
right: DEFAULT_AXIS_SIZE,
};
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 dubious about this approach. For me either you have an axis. ANd then this axis can have a default width/height properties. But the axis size should not have another default value than 0.

I don't see why it should get 30px space whereas their is no axis displayed

Copy link
Member Author

Choose a reason for hiding this comment

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

True, I was copying the previous sizes, (margin = 50, now margin = 20, axis = 30) but it doesn't make sense indeed. 😆

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 there a way to know which axis is actually going to be displayed beforehand? 🤔

My current issue is that the "default" axis, is added to the "available axis array", and counted towards sizing, even when not used. Any declared, but not used axis would count towards the "axisSize" with the current logic.

In the axis Components we have these:

  // Skip axis rendering if no data is available
  // - The domain is an empty array for band/point scales.
  // - The domains contains Infinity for continuous scales.
  if ((ordinalAxis && domain.length === 0) || (!ordinalAxis && domain.some(isInfinity))) {
    return null;
  }

So I assume we can only get the scale domain after processing the data, which means I'll probably have to move this to selectors I guess 😢

Comment on lines 243 to 245
return { ...acc, top: acc.top + (cur.height || 0) };
}
return { ...acc, bottom: acc.bottom + (cur.height || 0) };
Copy link
Member

Choose a reason for hiding this comment

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

What about moving the default here

Suggested change
return { ...acc, top: acc.top + (cur.height || 0) };
}
return { ...acc, bottom: acc.bottom + (cur.height || 0) };
return { ...acc, top: acc.top + (cur.height || DEFAULT_AXIS_HEIGHT) };
}
return { ...acc, bottom: acc.bottom + (cur.height || DEFAULT_AXIS_HEIGHT) };

Comment on lines +12 to +17
// TODO: fix dep cycle, should we move selectors to their own folder/file? Eg:
// useChartCartesianAxis/selectors/chartXAxis.ts
import {
selectorChartXAxis,
selectorChartYAxis,
} from '../../featurePlugins/useChartCartesianAxis/useChartCartesianAxis.selectors';
Copy link
Member Author

Choose a reason for hiding this comment

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

@mui/xcharts the current file setup is prone to dep cycles unrelated to the needed selectors. Should we move to more unitary files for selectors?

(acc, id) => {
const axis = xAxis.axis[id];

// TODO: move this to the selectorChartXAxis, axis.isUsed? axis.shouldRender?
Copy link
Member Author

Choose a reason for hiding this comment

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

@mui/xcharts what do you think about moving this logic as a permanent part of the axis definition? This can then be used in the x/y axis renderers

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.

4 participants