mui / mui-x

MUI X: Build complex and data-rich applications using a growing list of advanced React components, like the Data Grid, Date and Time Pickers, Charts, and more!
https://mui.com/x/
4.14k stars 1.29k forks source link

[charts] barChart's handling of non-unique values in xAxis.data is broken #14645

Open andreimatei opened 1 month ago

andreimatei commented 1 month ago

Steps to reproduce

<BarChart
  xAxis={[
    {
      scaleType: "band",
      data: ["a", "a", "a", "d", "f", "g"],
    },
  ]}
  series={[
    {
      data: [1, 2, 3, 4, 5, 6],
    },
  ]}
  width={500}
  height={300}
/>

https://codesandbox.io/p/sandbox/proud-frog-35fkjj?file=%2Fsrc%2FDemo.tsx%3A8%2C1-22%2C19

Try hovering over the bars and you'll see that the highlighting is not working right. Also, I get only one bar labeled "a" (I'm not sure with what value) whereas I'd like 3 of them.

Current behavior

The bars corresponding to the repeated labels are not drawn, and the tooltips appear messed up -- they behave kinda as if the missing bars were drawn.

Expected behavior

I'd like the following chart to result, as kindly drawn by @JCQuintas on Discord (except I'd like the same label "a" to show up on 3 consecutive bars).

Screenshot_2024-09-16_at_11 26 40

Context

In my case, I have repeated labels because I'm drawing a histogram of execution latencies, and sometimes buckets correspond to small enough time intervals that consecutive buckets get the same human readable label such as "1ms" (i.e. I don't name them 1.1ms, 1.2ms, etc).

Your environment

No response

Search keywords: duplicate xaxis values

michelengelen commented 1 month ago

I am pretty sure this is not supported right now, but it makes sense to have that. One way to achieve this would be to have a valueFormatter and store the data in the actual values. Example:

import * as React from 'react';
import { BarChart } from '@mui/x-charts/BarChart';

function roundToDecimal(value, decimals) {
  const factor = Math.pow(10, decimals);
  return Math.round(value * factor) / factor;
}

export default function BasicLineChart() {
  return (
    <>
      <BarChart
        xAxis={[
          {
            scaleType: 'band',
            data: [
              1.129411, 1.132464, 1.12632948, 1.519274, 1.81233123, 2.158988, 2.52349, 3.1112345,
            ],
            valueFormatter: (value) => `${roundToDecimal(value, 2)}ms`,
          },
        ]}
        series={[
          {
            data: [1, 2, 3, 4, 5, 6, 3, 4],
          },
        ]}
        width={700}
        height={300}
      ></BarChart>
    </>
  );
}

Would this work for you?

michelengelen commented 1 month ago

Result:

Screenshot 2024-09-17 at 09 52 26
michelengelen commented 1 month ago

I do agree though that we might want to allow for 'non-unique' values in the axis data. Not sure about the implications though. @JCQuintas ideas?

alexfauquette commented 1 month ago

Implications are important, because if you have multiple "A" values, how do you distinguish them?

For me the formatted is the solution.

An additional advantage of providing the correct value in you usecase could be to have more precision (1.1ms, 1.2ms, ...) in the tooltip

Since it's a common usecase, I propose to add a demo in the axis page

JCQuintas commented 1 month ago

I would assume this is fair use. The idea of "band" should assume all values are discrete, regardless of their actual value.

We could somewhat achieve this by changing the way band works, and instead of having only the scale we could have an alternate scale for plotting

plotScale: scaleBand(axis.data.map((_, i) => i)

It would probably require changes in multiple places though

andreimatei commented 1 month ago

One way to achieve this would be to have a valueFormatter and store the data in the actual values.

Indeed, this is similar to what I had come up with and how I moved forward, so I'm not blocked on this.

Still, to add my 2c, if I want to generate the following image, I would naively expect the following to just work:

  xAxis={[
    {
      scaleType: "band",
      data: ["a", "a", "a", "d", "f", "g"],
    },
  ]}

bars

I was surprised to see that the chart library cares about the values in data, and I am curious why it cares. Are there any benefits / are there cases where I benefit from the library being somehow smart and aggregating across these duplicates somehow?

alexfauquette commented 1 month ago

I was surprised to see that the chart library cares about the values in data, and I am curious why it cares. Are there any benefits / are there cases where I benefit from the library being somehow smart and aggregating across these duplicates somehow?

For the why, it's a consequences of the usage of d3 band scale which detect that there is duplication and remove them from the axis slice.

For bar chart I don't see any interest, because the item order matters, and I don't see how you could provide correct information for each item.

For other plots such as scatter chart it might be more interesting since a series could have multiple points in a category "A". But I don't think we support band scale for scatter chart for now.

michelengelen commented 1 month ago

@alexfauquette i'll let you decide how we should proceed with this issue. šŸ‘šŸ¼