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

[charts] Add Legend actions

Open JCQuintas opened this issue 1 month ago • 14 comments

Fixes https://github.com/mui/mui-x/issues/19455 Fixes https://github.com/mui/mui-x/issues/12344

Adds action to hide series/item when clicking on their legend.

Works by providing toggleVisibilityOnClick to the chart component's slot props.

slotProps={{
  legend: {
    toggleVisibilityOnClick: true,
  },
}}

Added a new VisibilityManager plugin.

New Public API

hideItem: (identifier: string | (string | number)[]) => void;
showItem: (identifier: string | (string | number)[]) => void;
toggleItem: (identifier: string | (string | number)[]) => void;

Currently available for:

Pie

output (1)

Scatter

output (3)

Radar

output (4)

Line

output (2)

Bar

output

Open questions

Should missing charts types also have the behaviour?

  • Heatmap: probably not. It doesn't have a default legend, and usually uses the continuous one.
  • Sankey: no legend, we might still want to hide/show nodes, which can be done with the VisibilityManager eventually
  • Funnel: can be done, not sure it is useful, it felt a bit weird removing a slice, and seemed to have no purpose

Should we animate Scatter and Radar interaction?

Right now both don't have any animation, so I just don't render the component. If we want animations we will need to handle those.

JCQuintas avatar Nov 19 '25 23:11 JCQuintas

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

Updated pages:

Bundle size report

Bundle size will be reported once CircleCI build #704162 finishes.

Generated by :no_entry_sign: dangerJS against b06a1800e7706b1e899671e91594294e8bb50417

mui-bot avatar Nov 19 '25 23:11 mui-bot

CodSpeed Performance Report

Merging #20404 will not alter performance

Comparing JCQuintas:legend-actions (e200382) with master (882da2c)[^unexpected-base] [^unexpected-base]: No successful run was found on master (5405d77) during the generation of this report, so 882da2c was used instead as the comparison base. There might be some changes unrelated to this pull request in this report.

Summary

✅ 13 untouched

codspeed-hq[bot] avatar Nov 20 '25 14:11 codspeed-hq[bot]

Should missing charts types also have the behaviour?

I don't think so. Looks weird for now. Lets wait to see if there are real usecases

Should we animate Scatter and Radar interaction?

Doe sit impact a lot the technical solution? I don't think users will ask for it, so for now not having them looks ok

alexfauquette avatar Nov 20 '25 16:11 alexfauquette

Should we animate Scatter and Radar interaction?

Doe sit impact a lot the technical solution? I don't think users will ask for it, so for now not having them looks ok

It is a different approach, but it is not hard. It is just that right now they don't have any transition animation, so I wasn't sure it would make sense to add animations for these specific interactions.

Maybe we keep it out for now and take a further look once we divide charts up in composable parts in the future

JCQuintas avatar Nov 20 '25 16:11 JCQuintas

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

github-actions[bot] avatar Nov 27 '25 10:11 github-actions[bot]

From the gifs, I can see that we are not changing the axis range based on hiding/viewing a series.

We can keep it out of scope, until users request for it. Later on, a setting like Zoom filtermode: discard/keep, could be helpful for some use cases.

prakhargupta1 avatar Dec 01 '25 10:12 prakhargupta1

From the gifs, I can see that we are not changing the axis range based on hiding/viewing a series.

We can keep it out of scope, until users request for it. Later on, a setting like Zoom filtermode: discard/keep, could be helpful for some use cases.

Yeah, I intend to add it as a future iteration. Will create an issue so we don't forget 😆

JCQuintas avatar Dec 01 '25 13:12 JCQuintas

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

github-actions[bot] avatar Dec 04 '25 11:12 github-actions[bot]

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

github-actions[bot] avatar Dec 04 '25 14:12 github-actions[bot]

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

github-actions[bot] avatar Dec 04 '25 15:12 github-actions[bot]

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

github-actions[bot] avatar Dec 05 '25 13:12 github-actions[bot]

One solutoin for your staking issues

index c6c0b2e03e..c5b04c7c72 100644
--- a/packages/x-charts/src/BarChart/seriesConfig/bar/seriesProcessor.ts
+++ b/packages/x-charts/src/BarChart/seriesConfig/bar/seriesProcessor.ts
@@ -86,7 +86,12 @@ const seriesProcessor: SeriesProcessor<'bar'> = (params, dataset, isIdentifierVi
       .offset(stackingOffset)(d3Dataset);
 
     // We sort the keys based on the original stacking order to ensure consistency
-    const idOrder = stackedData.sort((a, b) => a.index - b.index).map((s) => s.key);
+    const idOrder = stackedData.map((s) => s.key);
+
+    const idToIndexMap: { [key: string]: number } = {};
+    idOrder.forEach((id, index) => {
+      idToIndexMap[id] = index;
+    });
 
     // Compute visible stacked data
     const visibleStackedData = d3Stack<any, DatasetElementType<number | null>, SeriesId>()
@@ -120,8 +125,8 @@ const seriesProcessor: SeriesProcessor<'bar'> = (params, dataset, isIdentifierVi
         ...series[id],
         data,
         hidden,
-        stackedData: stackedData[index] as [number, number][],
-        visibleStackedData: visibleStackedData[index] as [number, number][],
+        stackedData: stackedData[idToIndexMap[id]] as [number, number][],
+        visibleStackedData: visibleStackedData[idToIndexMap[id]] as [number, number][],
       };
     });
   });

About the diverging I looked at the D3 implementation

function d3StackOffsetDiverging(series, order) {
  if (!((n = series.length) > 0)) return;
  for (var i, j = 0, d, dy, yp, yn, n, m = series[order[0]].length; j < m; ++j) {
    for (yp = yn = 0, i = 0; i < n; ++i) {
      if ((dy = (d = series[order[i]][j])[1] - d[0]) > 0) {
        d[0] = yp, d[1] = yp += dy;
      } else if (dy < 0) {
        d[1] = yn, d[0] = yn += dy;
      } else {
		// Here I would like to make a distinction between if the series has a negative or positive value when visible 
		// Such that if negative, we do d[0] = yn, d[1] = yn;
		// if positive, we do d[0] = yp, d[1] = yp;
        d[0] = 0, d[1] = dy;
      }
    }
  }
}

alexfauquette avatar Dec 09 '25 14:12 alexfauquette

One solutoin for your staking issues

Which solution is this trying to fix? 🤔 I put it in but put couldn't figure the difference

JCQuintas avatar Dec 09 '25 22:12 JCQuintas

The version I tried was before you pushed your fix https://github.com/mui/mui-x/pull/20404/commits/9cc928a0ee676e5ff21c442acf6f78ccd9c59c8f

alexfauquette avatar Dec 10 '25 08:12 alexfauquette