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] Allow setting a custom marker in legends and tooltips #16654

Open
wants to merge 14 commits into
base: master
Choose a base branch
from

Conversation

bernardobelchior
Copy link
Member

@bernardobelchior bernardobelchior commented Feb 19, 2025

Related to #16640.

Allow setting a custom marker in legends and tooltips. This will be useful because scatter charts with custom shapes will probably want to have legends/tooltips with custom shapes.

Open questions:

  1. Should we also allow configuring the label mark of piecewise and continuous legends? I don't think it makes sense for continuous legends, but it might for piecewise? I'm not familiar with them, so I'm not sure of the use case.

Left heatmap out in this PR because its tooltip implementation it separate. Will implement it in a follow-up PR, along with documentation.

@mui-bot
Copy link

mui-bot commented Feb 19, 2025

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 Feb 20, 2025
@bernardobelchior
Copy link
Member Author

@alexfauquette @JCQuintas, a small question regarding API design. After I started working on adding custom shapes to scatter charts, I realized we'll probably want the scatter shape to match the legend/tooltip shape.
Do you have any suggestions on how to approach this?
In this PR I'm creating a labelMark slot for all charts that customizes the shape for legends and tooltips, but that doesn't solve the issue for custom scatter shapes. In this other PR I'm create a marker slot for scatter charts, but then a user that wants matching scatter shape and legend, they would need to provide two slots. What do you think of that? It's slightly more verbose, but maybe more flexible?

@github-actions github-actions bot removed the PR: out-of-date The pull request has merge conflicts and can't be merged label Feb 20, 2025
Copy link
Member

@JCQuintas JCQuintas left a comment

Choose a reason for hiding this comment

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

Very clean implementation, nice work.

Left a comment of something that is probably important in some use-cases

Comment on lines +11 to +15
/**
* ID of the series this mark refers to.
*/
seriesId?: SeriesId;
Copy link
Member

Choose a reason for hiding this comment

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

Probably needs dataIndex as well, since for PieCharts the entries are based on that 😢

Copy link
Member Author

Choose a reason for hiding this comment

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

Good point! I'll create a demo for pie charts, then 👍

Copy link
Member Author

Choose a reason for hiding this comment

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

Glad I tried to create a demo. It turns out the API didn't allow for the same customization in pie charts as it did for others 😄

Co-authored-by: Alexandre Fauquette <[email protected]>
Signed-off-by: Bernardo Belchior <[email protected]>
@bernardobelchior bernardobelchior marked this pull request as ready for review February 21, 2025 10:17
Comment on lines +43 to +46
/**
* The data index of the pie item
*/
dataIndex?: number;
Copy link
Member

Choose a reason for hiding this comment

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

Sorry, in this case the itemId is already normalised to be id ?? dataIndex 😅

Copy link
Member Author

@bernardobelchior bernardobelchior Feb 21, 2025

Choose a reason for hiding this comment

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

Yeah, but I find that a bit weird. If the user receives a id ?? dataIndex, how do they know if they should search for the ID or the data index? We don't enforce that all data points have an ID, so the user won't know whether to do data.find(d => d.id === itemId) or data[itemId].

Now that I think of it, dataIndex makes more sense because it's always present, while the ID is optional. In my opinion, we should always provide dataIndex for those reasons, but we can consider also providing itemId if you think it's useful.

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.

So replace itemId with dataIndex? I could agree with that. I think I just didn't think critically about this specific part when making the id optional on pie items

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, either the ID should be mandatory or we should use dataIndex. I'm not sure if the ID should be mandatory, though, so I guess dataIndex is probably the best option. 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.

We recently removed the requirement to have the id, it was required previously, so the correct way would be to use the dataIndex here. It is a breaking change though

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.

I'm not sure about the API.

Using the seriesId (and/or dataIndex) feels weird for real usecases. It forces users to take care of keeping in sync theirs slots and the data configuration.

From what I remember @JCQuintas The initial discussion was to allow either

Allowing to pass component like labelMark: 'line' | 'circle' | React.Elementto get{data: ...., labelMark: }` and is such case we render the component. Easy, without having to take care of slotsProps.

The other with slots is to allow labelMark extensions, and then it solves the pie chart issues by adding labelMark in the pie items.

Comment on lines +5 to +9
{ id: 0, data: [10], label: 'Series A' },
{ id: 1, data: [15], label: 'Series B' },
{ id: 2, data: [20], label: 'Series C' },
{ id: 3, data: [10], label: 'Series D' },
];
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
{ id: 0, data: [10], label: 'Series A' },
{ id: 1, data: [15], label: 'Series B' },
{ id: 2, data: [20], label: 'Series C' },
{ id: 3, data: [10], label: 'Series D' },
];
{ id: 0, data: [10, 15], label: 'Series A' },
{ id: 1, data: [15, 20], label: 'Series B' },
{ id: 2, data: [20, 25], label: 'Series C' },
{ id: 3, data: [10, 15], label: 'Series D' },
];

return (
<BarChart
series={seriesConfig}
xAxis={[{ scaleType: 'band', data: ['A'] }]}
Copy link
Member

Choose a reason for hiding this comment

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

Not directly related to the PR, but the axis "A" and series "Series A" bugs me (or to be precise let me think their is a bug in the tooltip

image

Suggested change
xAxis={[{ scaleType: 'band', data: ['A'] }]}
xAxis={[{ scaleType: 'band', data: ['cat. 1', 'cat. 2'] }]}

Comment on lines +74 to +77
For more advanced use cases, you can also provide a component to the `labelMark` slot to fully customize the mark.

{{"demo": "LegendCustomLabelMark.js" }}

Copy link
Member

Choose a reason for hiding this comment

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

Maybe also adding a h4 title "Built-in shapes" in the previous section.

It's mostly to simplify to visual processing of information. Titles are useful to split text and demos in coherent bloks.

For example here without reading I can't guess to which demos the paragraph refers to

image

Suggested change
For more advanced use cases, you can also provide a component to the `labelMark` slot to fully customize the mark.
{{"demo": "LegendCustomLabelMark.js" }}
#### Custom shapes
For more advanced use cases, you can also provide a component to the `labelMark` slot to fully customize the mark.
{{"demo": "LegendCustomLabelMark.js" }}

@bernardobelchior
Copy link
Member Author

Using the seriesId (and/or dataIndex) feels weird for real usecases. It forces users to take care of keeping in sync theirs slots and the data configuration.

Yeah, that's a good point. The reason I did this was for consistency with the other PR for custom shapes where I'm also using slots. Maybe I should move away from slots in that example as well?

From what I remember @JCQuintas The initial discussion was to allow either

Allowing to pass component like labelMark: 'line' | 'circle' | React.Elementto get{data: ...., labelMark: }` and is such case we render the component. Easy, without having to take care of slotsProps.

Yeah, that API could work. The only downside I see is that if someone wants to use the same custom shape for all series, then they'd have to set the prop on every series, but I guess that's an acceptable trade-off.

The other with slots is to allow labelMark extensions, and then it solves the pie chart issues by adding labelMark in the pie items.

Sorry, I didn't get this option. What do you mean by "labelMark extensions"?

@JCQuintas
Copy link
Member

My initial proposal for labelMarkType was to have something like the code below

interface LabelMarkTypeExtension {}

// Series config ...
labelMarkType?: 'square' | 'circle' | 'line' | keyof LabelMarkTypeExtension

// Custom Legend/Tooltip/Slot
const Legend = ({labelMarkType}) => {
  // decide what to do based on mark type.
}

I think the current implementation of slots would still be necessary, so it is a step towards that. And I would leave the labelMarkType changes to a different PR to not make it more confusing 😆

Providing seriedId|dataIndex might still be useful in some cases, but we can remove that to make the api cleaner.

Then on a future PR we implement the above, which would make the api more flexible from the series configuration side.

This way you would have one place to define the component if you want all of them to use the same thing, while if you want a more extensive approach you would have to use the slot+typeExtension+seriesConfig.

Accepting a labelMarkType?: ... | React.Element would be possible too, which would skip the typeExtension step, but I can't think about the advantages/drawbacks of each approach. It would probably make the slot useless like @alexfauquette mentioned, but at the cost of changing all the marks in one go. 🤷

@alexfauquette
Copy link
Member

but at the cost of changing all the marks in one go. 🤷

Wy all the marks? The following would be still feasible. It only enforce on mark per series

{ id: 'line-1', labelMarkType: CustomMark1 },
{ id: 'line-2', labelMarkType: CustomMark2 },
{ id: 'line-1', labelMarkType: 'circle' },

@bernardobelchior
Copy link
Member Author

So, what you're proposing is accepting a component in labelMarkType? Or should I also support slot?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants