12

I'm Working on Chart.js, wanted to implement Select All and Unselect All option for labels. I'm trying to find it out but couldn't get anything such.

Please do help me out if anything such feature can be implemented.

David
  • 539
  • 3
  • 9
  • 23

5 Answers5

27

If you need to show/hide charts selecting/unselecting all labels here is an example:

var barChartData = {
  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, 31, 32],
  datasets: [{
    label: 'One',
    backgroundColor: 'rgba(206, 0, 23, 1)',
    data: [0, 1, 3, 0, 2, 0, 0, 2, 0, 1, 0, 1, 1, 0, 0, 1, 0, 0, 2, 1, 0, 1, 2, 1, 1, 0, 0, 0, 2, 2, 0, 3]
  }, {
    label: 'Two',
    backgroundColor: 'rgba(206, 0, 23, 0.75)',
    data: [0, 0, 1, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1]
  }, {
    label: 'Three',
    backgroundColor: 'rgba(206, 0, 23, 0.5)',
    data: [0, 0, 1, 0, 0, 0, 0, 0, 1, 1, 0, 1, 0, 0, 1, 0, 0, 0, 1, 1, 0, 1, 2, 0, 0, 0, 1, 0, 0, 0, 0, 1]
  }]

};
var ctx = document.getElementById('canvas').getContext('2d');
var chartInstance = new Chart(ctx, {
  type: 'bar',
  data: barChartData,
  options: {
    title: {
      display: false,
    },
    responsive: true,
    scales: {
      xAxes: [{
        stacked: true,
      }],
      yAxes: [{
        stacked: true
      }]
    }
  }
});

$("#toggle").click(function() {
  chartInstance.data.datasets.forEach(function(ds) {
    ds.hidden = !ds.hidden;
  });
  chartInstance.update();
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.7.2/Chart.bundle.min.js"></script>
<button id="toggle">show/hide all</button>
<canvas id="canvas" height="500" width="800"></canvas>

Jsfiddle: https://jsfiddle.net/beaver71/00q06vjp/

Credits: see https://github.com/chartjs/Chart.js/issues/3009


Update: for pie chart use "meta", see: https://jsfiddle.net/beaver71/u0y0919b/

beaver
  • 17,333
  • 2
  • 40
  • 66
  • Beaver, it's not working for pie chart. I guess chartInstance.data.datasets in pie is giving different data attributes. any idea for pie chart ? – David Mar 13 '18 at 10:22
  • For pie chart you have to use "meta", see my fiddle: https://jsfiddle.net/beaver71/u0y0919b/ – beaver Mar 13 '18 at 10:46
  • Beaver, can you please help me here, right now first if I click some **individual label**, then **show/hide all** event doesn't apply to that particular clicked label. Is there any crack where we can apply **show/hide all** to all the labels by default no matter if he click **individual** or **show/hide all** – David Mar 14 '18 at 06:39
  • If you check the code carefully you see that the logic is basically `ds.hidden = !ds.hidden;`. So it inverts ds.hidden value... change this logic as you prefer... – beaver Mar 14 '18 at 16:12
  • yes you are right, but after clicking **individual labels** and then **show/hide button,** the values is changing to **true / false** but not reflecting in UI. Not getting how to handle this. – David Mar 15 '18 at 07:41
  • 2
    Beaver, I had edited your code and added logic to apply **show/hide** event to all the labels. The functionality is working fine but issue is that once I click on **show/hide** button, then **individual lables** works only after **double click instead of single click** for first time. here is fiddle link : https://jsfiddle.net/bhushand/31mbcnfq/6/ can you pls help me out how can I fix this issue. I want labels to work for first click instead of double click – David Apr 06 '18 at 06:56
3

The correct answer result in :

chart.data.datasets.forEach((obj, index) => {
   let meta = this.eval_chart.getDatasetMeta(index);
   meta.hidden = !meta.hidden || null;
});
chart.update();

As wrote in the documentation : https://www.chartjs.org/docs/latest/configuration/legend.html#custom-on-click-actions

Mathias Osterhagen
  • 402
  • 1
  • 3
  • 19
  • This is the correct answer, although for TypeScript you need to change it a bit, since meta is possibly undefined. So you risk getting exceptions if you run the code as is. – Mikkel Cortnum Jun 22 '23 at 14:21
3

V3 Answer

You can use a custom generateLabels function together with a custom onClick to get it as an extra legendItem like so:

const defaultLegendClickHandler = Chart.defaults.plugins.legend.onClick;
const pieDoughnutLegendClickHandler = Chart.controllers.doughnut.overrides.plugins.legend.onClick;

const options = {
  type: 'line',
  data: {
    labels: ["Red", "Blue", "Yellow", "Green", "Purple", "Orange"],
    datasets: [{
        label: '# of Votes',
        data: [12, 19, 3, 5, 2, 3],
        borderColor: 'pink'
      },
      {
        label: '# of Points',
        data: [7, 11, 5, 8, 3, 7],
        borderColor: 'orange'
      }
    ]
  },
  options: {
    plugins: {
      legend: {
        onClick: (evt, legendItem, legend) => {
          const type = legend.chart.config.type;
          let allLegendItemsState = [];

          if (legendItem.text === 'hide all datasets' || legendItem.text === 'show all datasets') {
            if (typeof legend.hideAll === 'undefined') {
              legend.hideAll = false;
            }

            legend.chart.data.datasets.forEach((dataset, i) => {
              legend.chart.setDatasetVisibility(i, legend.hideAll)
            });

            legend.hideAll = !legend.hideAll;
            legend.chart.update();

            return;
          }

          if (type === 'pie' || type === 'doughnut') {
            pieDoughnutLegendClickHandler(evt, legendItem, legend)
          } else {
            defaultLegendClickHandler(evt, legendItem, legend);
          }

          allLegendItemsState = legend.chart.data.datasets.map((e, i) => (legend.chart.getDatasetMeta(i).hidden));

          if (allLegendItemsState.every(el => !el)) {
            legend.hideAll = false;
            legend.chart.update();
          } else if (allLegendItemsState.every(el => el)) {
            legend.hideAll = true;
            legend.chart.update();
          }
        },
        labels: {
          generateLabels: (chart) => {
            const datasets = chart.data.datasets;
            const {
              labels: {
                usePointStyle,
                pointStyle,
                textAlign,
                color
              }
            } = chart.legend.options;

            const legendItems = chart._getSortedDatasetMetas().map((meta) => {
              const style = meta.controller.getStyle(usePointStyle ? 0 : undefined);

              return {
                text: datasets[meta.index].label,
                fillStyle: style.backgroundColor,
                fontColor: color,
                hidden: !meta.visible,
                lineCap: style.borderCapStyle,
                lineDash: style.borderDash,
                lineDashOffset: style.borderDashOffset,
                lineJoin: style.borderJoinStyle,
                strokeStyle: style.borderColor,
                pointStyle: pointStyle || style.pointStyle,
                rotation: style.rotation,
                textAlign: textAlign || style.textAlign,
                datasetIndex: meta.index
              };
            });

            legendItems.push({
              text: (!chart.legend.hideAll || typeof chart.legend.hideAll === 'undefined') ? 'hide all datasets' : 'show all datasets',
              fontColor: color,
              fillStyle: 'turquoise', // Box color
              strokeStyle: 'turquoise', // LineCollor around box
            });

            return legendItems;
          }
        }
      }
    }
  }
}

const ctx = document.getElementById('chartJSContainer').getContext('2d');
new Chart(ctx, options);
<body>
  <canvas id="chartJSContainer" width="600" height="400"></canvas>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/3.6.0/chart.js"></script>
</body>
LeeLenalee
  • 27,463
  • 6
  • 45
  • 69
2

For ChartJS 2.9.3, this works as requested by David in the comments:

const chart = ...
chart.data.datasets.forEach(dataset => {
  Object.keys(dataset._meta).forEach(key => {
    const current = !dataset._meta[key].hidden
    dataset._meta[key].hidden = current || null
  })
})
chart.update()

Toggles all with a button, while playing nicely with the individual toggling in the chart legend.

ivom
  • 123
  • 1
  • 6
  • Thanks! behaviour of individual toggling in the chart legend + my custom hide/show button was driving me crazy – Ivan Denysov Oct 21 '20 at 13:25
  • I'm using the react-chartjs-2 library. I wanted a select all / unselect all feature. This was helpful. For my line chart, if toggle {dataset._meta[0].hidden = true} else {dataset._meta[0].hidden = **null**}. I was originally using `false` instead of `null` but null ended up being the key to **avoid the double click issue** mentioned above. – Bilbo Jun 18 '21 at 15:27
0

For v4 and TypeScript, or if you want to make sure you don't get an exception because meta can be undefined, I used the following code below. I wanted to make sure it always toggled all to be in the same state (select/unselect all), hence the hidden variable outside the function scope.

My answer is inspired by @ivom's answer.

let hidden = false
function toggleAllDatasets() {
  if (!chart) {
    return
  }

  hidden = !hidden

  chart.data.datasets.forEach((obj, index) => {
    let meta = chart?.getDatasetMeta(index)
    if (!meta) {
      return
    }
    meta!.hidden = hidden
  });

  chart.update()
}
Mikkel Cortnum
  • 481
  • 4
  • 11