mui-x icon indicating copy to clipboard operation
mui-x copied to clipboard

[charts] Add Legend actions

Open JCQuintas opened this issue 1 year ago • 5 comments

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

chartjs recharts visx

JCQuintas avatar Apr 10 '24 08:04 JCQuintas

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

Generated by :no_entry_sign: dangerJS against f46c59883bdbaeef20c13c511cb000714d1b8381

mui-bot avatar Apr 10 '24 09:04 mui-bot

Other benchmark

From other libraries two behaviors are often built in and seem useful:

Echarts Highcharts

  1. On hover the series is highlighted and others are faded
  2. 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[].color to something akin to dataset[].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

alexfauquette avatar Apr 10 '24 10:04 alexfauquette

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?

JCQuintas avatar Apr 15 '24 09:04 JCQuintas

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

image

alexfauquette avatar Apr 15 '24 12:04 alexfauquette

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

github-actions[bot] avatar May 13 '24 09:05 github-actions[bot]

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

codspeed-hq[bot] avatar Aug 16 '24 09:08 codspeed-hq[bot]