[charts] Add Legend actions
Event Handlers Approach (current)
The current implementation is a naive approach where I simply added a onClick handler to each of the labels, and let the users manually handle the changes they want.
You should be able to test it with the code below.
import * as React from 'react';
import { BarChart } from '@mui/x-charts/BarChart';
import { Box } from '@mui/material';
const chartSetting = {
yAxis: [{ label: 'rainfall (mm)' }],
height: 300,
};
const dataset = [
[59, 57, 86, 21, 'Jan'],
[50, 52, 78, 28, 'Fev'],
[47, 53, 106, 41, 'Mar'],
[54, 56, 92, 73, 'Apr'],
[57, 69, 92, 99, 'May'],
[60, 63, 103, 144, 'June'],
[59, 60, 105, 319, 'July'],
[65, 60, 106, 249, 'Aug'],
[51, 51, 95, 131, 'Sept'],
[60, 65, 97, 55, 'Oct'],
[67, 64, 76, 48, 'Nov'],
[61, 70, 103, 25, 'Dec'],
].map(([london, paris, newYork, seoul, month]) => ({ london, paris, newYork, seoul, month }));
const valueFormatter = (value: number | null) => `${value}mm`;
function BarsDataset() {
const [series, setSeries] = React.useState([
{ dataKey: 'london', label: 'London', valueFormatter, color: 'rgba(255, 0, 0, 1)' },
{ dataKey: 'paris', label: 'Paris', valueFormatter, color: 'rgba(0, 255, 0, 1)' },
{ dataKey: 'newYork', label: 'New York', valueFormatter, color: 'rgba(0, 0, 255, 1)' },
{ dataKey: 'seoul', label: 'Seoul', valueFormatter, color: 'rgba(255, 0, 255, 1)' },
]);
return (
<Box
sx={{
[`& .MuiChartsLegend-seriesBackground:hover`]: {
fill: 'rgba(10,10,50,0.3)',
},
}}
>
<BarChart
dataset={dataset}
xAxis={[{ scaleType: 'band', dataKey: 'month' }]}
series={series}
slotProps={{
legend: {
onClick: (item, index) => {
console.log(item, index);
setSeries((prev) => {
const newSeries = [...prev];
if (newSeries[index].color.endsWith('1)')) {
newSeries[index] = {
...newSeries[index],
color: newSeries[index].color.replace('1)', '0.3)'),
};
} else {
newSeries[index] = {
...newSeries[index],
color: newSeries[index].color.replace('0.3)', '1)'),
};
}
return newSeries;
});
},
},
}}
{...chartSetting}
/>
</Box>
);
}
export default function Page() {
return <BarsDataset />;
}
https://codesandbox.io/p/sandbox/bold-leftpad-knlpjp?file=%2Fsrc%2FDemo.tsx%3A74%2C21
It should eventually also have events for mouseover/enter/leave/etc
Class approach
We could build on top of the current approach by also adding classes to both the legends and data(bar/point/line/etc) related to the series.
Eg: mouseover legend for series "london". We add a MuiCharts(tdb)-hover to all legends and data related to that series. On click we would add an active class, while removing it from the previous elements if any.
This would allow users to control these behaviours by simply implementing their own css.
Fully managed
We could work on top of both previous ideas and "fully manage" the changes, if we change the dataset[].color to something akin to dataset[].colors:{hover,active,etc}
This way we would be able to handle all the changes ourselves with the data provided
Question
- How flexible we want this feature to be?
References
Deploy preview: https://deploy-preview-12730--material-ui-x.netlify.app/
Generated by :no_entry_sign: dangerJS against f46c59883bdbaeef20c13c511cb000714d1b8381
Other benchmark
From other libraries two behaviors are often built in and seem useful:
- On hover the series is highlighted and others are faded
- On click the series is hidden
On the second point, Echarts as a notion of "selection":
Related issue
Hidding series
The notion of hidden series does not exist yet, and could not be implemented by users, because if they filter series they will disappear from the legend
Highlighted
The notion of Highlight already exists. We also have a request to make it controllable: https://github.com/mui/mui-x/issues/12351
Personal opinion
Having the enter/leave/click event on the legend seems good enough for a first step.
About adding custom classes, I don't know. Most of the series already have classes like .MuiLineElement-series-[the series id] so if users are saving which series id to modify they can already target them.
We could work on top of both previous ideas and "fully manage" the changes if we change the
dataset[].colorto something akin todataset[].colors:{hover,active,etc}
I struggled to understand, but I assume you mean series instead of dataset.
It's a promising direction. I tend to prefer the Echarts approach which defines series.emphasized = {color, strokeWidth, ...}, and series.faded = {color, strokeWidth, ...} allowing users to override how the series looks in various states by overriding properties. But it seems out of the scope of this PR
This is still broken, way more complex than what I anticipated. 😅
@alexfauquette if you could take a look at my last commit. This "technically" makes the legend work, while breaking a lot of other things.
Do you see a clear way for this to work? I was thinking of maybe adding a enter/leave legend with a flag that prevents the "exitchart" from resetting the state?
I can see multiple options
1. Triggering exit chart only once
By having a React state isInDrawingArea in the useAxisEvent and there ChartsVoronoidHandler you could prevent the dispatch of this event if the mouse was not inside the drawing area before
2. Save the source source of the event.
The context could have an additional property such as source: 'drawingArea' | 'legend' that indicates if the current highlight state comes from the legend trigger of the drawing area.
I don't like that one because if you allow users to control this highlight state, it becames complicated.
3. Add a third option
We currently have axis, and item properties. we could add series. But I'm not completely convinced because it hoverflow a bit with the item
Last thaught on the topic, we will soon have legend for other properties than series. For example threshold colormap will imply to create legend by values and not by series
This pull request has conflicts, please resolve those before we can evaluate the pull request.
CodSpeed Performance Report
Merging #12730 will not alter performance
Comparing JCQuintas:12344-charts-legend-actions-eg-highlighting (f46c598) with master (c4568a9)
Summary
✅ 3 untouched benchmarks