[Chartjs]-Hide or show two datasets with one click event of legend in chart.js

4👍

First you need to define a legend.labels.generateLabels function that filter out undesired legend labels and changes the text of the remaining ones.

generateLabels: chart => chart.data.datasets.map((ds, i) => ({
    text: ds.label.substring(0, ds.label.indexOf('-')),
    datasetIndex: i,
    fillStyle: chart.data.datasets[i].backgroundColor,
    strokeStyle: chart.data.datasets[i].backgroundColor,
    hidden: chart.getDatasetMeta(i).hidden
  }))
  .filter((ds, i) => i % 2),

Then you’ll have to define a legend.onClick function that takes care of also showing/hiding the sibling dataset when the user clicks on a legend label.

onClick: (event, legendItem, legend) => {
  let hidden = !legend.chart.getDatasetMeta(legendItem.datasetIndex).hidden;
  (legendItem.datasetIndex == 1 ? [0, 1] : [2, 3])
  .forEach(i => legend.chart.getDatasetMeta(i).hidden = hidden);
  legend.chart.update();
}

For further details, see the Legend and API chapters of the Chart.js documentation.

Please take a look at your amended code and see how it works.

new Chart('chart', {
  type: 'bar',
  data: {
    labels: ["1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15", "16", "17", "18", "19", "20", "21", "22", "23", "24", "25", "26", "27", "28", "29", "30", ],
    datasets: [{
        label: 'Machine 1 - Day',
        stack: 'Stack 0',
        data: [5, 13, 5, 20, 4, 9, 28, 19, 21, 5, 13, 7, 21, 26, 10, 28, 19, 21, 30, 10, 27, 6, 12, 15, 4, 2, 13, 8, 29, 30],
        backgroundColor: '#FF4A4A',
      },
      {
        label: 'Machine 1 - Night',
        stack: 'Stack 1',
        data: [5, 13, 5, 20, 4, 9, 28, 19, 21, 5, 13, 7, 21, 26, 10, 28, 19, 21, 30, 10, 27, 6, 12, 15, 4, 2, 13, 8, 29, 30],
        backgroundColor: '#FF4A4A',
      },
      {
        label: 'Machine 2 - Day',
        stack: 'Stack 0',
        data: [5, 13, 5, 20, 4, 9, 28, 19, 21, 5, 13, 7, 21, 26, 10, 28, 19, 21, 30, 10, 27, 6, 12, 15, 4, 2, 13, 8, 29, 30],
        backgroundColor: '#FF9C2A',
      },
      {
        label: 'Machine 2 - Night',
        stack: 'Stack 1',
        data: [12, 13, 5, 20, 4, 9, 28, 19, 21, 5, 13, 7, 21, 26, 10, 28, 19, 21, 30, 10, 27, 6, 12, 15, 4, 2, 13, 8, 29, 30],
        backgroundColor: '#FF9C2A',
      }
    ]
  },
  options: {
    plugins: {
      legend: {
        labels: {
          generateLabels: chart => chart.data.datasets.map((ds, i) => ({
              text: ds.label.substring(0, ds.label.indexOf('-')),
              datasetIndex: i,
              fillStyle: chart.data.datasets[i].backgroundColor,
              strokeStyle: chart.data.datasets[i].backgroundColor,
              hidden: chart.getDatasetMeta(i).hidden
            }))
            .filter((ds, i) => i % 2),
        },
        onClick: (event, legendItem, legend) => {
          let hidden = !legend.chart.getDatasetMeta(legendItem.datasetIndex).hidden;
          (legendItem.datasetIndex == 1 ? [0, 1] : [2, 3])
          .forEach(i => legend.chart.getDatasetMeta(i).hidden = hidden);
          legend.chart.update();
        }
      }
    },
    scales: {
      x: {
        stacked: true,
      },
      y: {
        stacked: true
      }
    }
  }
});
canvas {
  max-height: 190px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/3.7.0/chart.js"></script>
<canvas id="chart"></canvas>

0👍

Hey I got something like this working, but I am hiding everything with the same stack variable and hiding certain data series as well so I can hide them without having their legend items available for click:

I have a lot of series that have the same datasets (ie, failed numbers, retested numbers and dont want to show those labels)

// Get Data From Views and Add Custom onClick Listener
let tempData = { ...views[i].data };
if (
// Kill the onClick and add a much slower but grouping onClick, hide all with part number in stack
    tempData &&
    tempData.options &&
    tempData.options.plugins &&
    tempData.options.plugins.legend
)
    tempData.options.plugins.legend.onClick = (e, legendItem) => {
        // My labels always start with the part number and a space so I grab that.
        const partName = legendItem.text.split(' ')[0];
        const chart = Chart.getChart('temp-chart');

        // Toggle the data set that was clicked on
        const datasetIndex = legendItem.datasetIndex;
        if (!chart.getDatasetMeta(datasetIndex).hidden) chart.hide(datasetIndex);
        else chart.show(datasetIndex);

        let j = 0;
        // Iterate through all datasets, cap at 200 for safety
        while (chart.getDatasetMeta(j).stack && j < 200) {
            const datasetMeta = chart.getDatasetMeta(j);
            const stack = datasetMeta.stack;
            const datasetName = datasetMeta.label;

            // I add a stack id to all my datasets and use that to determine what is 'grouped'
            // I have a plugin that does not render any datasets which do not have a label
            // If the stack group matches and the dataset does not have a label it will be hidden.
            if (
                stack === partName &&
                (datasetName == null ||
                    datasetName === '' ||
                    datasetName === 'undefined')
            ) {
                if (!chart.getDatasetMeta(j).hidden) chart.hide(j);
                else chart.show(j);
            }
            j++;
        }
    };
new Chart('temp-chart', { ...tempData });

And the legend plugin to hide legend items with no label.

Chart.defaults.plugins.legend.labels.filter = (item) => {
    return item.text != null;
};

Heres what I got – Pictures

Leave a comment