Chartjs-Chart.js – How to display all bars as different datasets(same behaviour as doughnut)

1πŸ‘

βœ…

It seems that you’ll have to generate custom HTML legend using legendCallback together with some CSS.

legendCallback: chart => {
  let html = '<ul>';
  chart.data.labels.forEach((l, i) => {
    const ds = chart.data.datasets[0];
    const bgColor = ds.backgroundColor[i];
    const border = ds.borderWidth + 'px solid ' + ds.borderColor[i];
    html += '<li>' +
      '<span style="width: 36px; height: 14px; background-color:' + bgColor + '; border:' + border + '" onclick="onLegendClicked(event, \'' + i + '\')">&nbsp;</span>' +
      '<span id="legend-label-' + i + '" onclick="onLegendClicked(event, \'' + i + '\')">' +
      (Array.isArray(l) ? l.join('<br/>') : l) + '</span>' +
      '</li>';
  });
  return html + '</ul>';
},

To make this behave the same as standard Chart.js charts, the function onLegendClicked is invoked when a mouse click occurs on a legend label. This function redefines data.labels and the dataset (data, backgroundColor and borderColor) by keeping trak of the hidden state of individual bars. It also changes the legend labels text style between normal and strike-through.

Please take a look at the following code snippet and see how it works.

const labels = ["January", "February", "March", "April", "May", "June", "July"];
const data = [65, 59, 80, 81, 56, 55, 40];
const backgroundColor = ["rgba(255, 99, 132, 0.2)", "rgba(255, 159, 64, 0.2)", "rgba(255, 205, 86, 0.2)", "rgba(75, 192, 192, 0.2)", "rgba(54, 162, 235, 0.2)", "rgba(153, 102, 255, 0.2)", "rgba(201, 203, 207, 0.2)"];
const borderColor = ["rgb(255, 99, 132)", "rgb(255, 159, 64)", "rgb(255, 205, 86)", "rgb(75, 192, 192)", "rgb(54, 162, 235)", "rgb(153, 102, 255)", "rgb(201, 203, 207)"];

const states = labels.map((l, i) => ({index: i, hidden: false}));

function onLegendClicked(e, i) {
  let hidden = !states[i].hidden; 
  states[i].hidden = hidden;  
  let nonHiddenIndexes = states.filter(s => !s.hidden).map(s => s.index);
  chart.data.labels = nonHiddenIndexes.map(i => labels[i]);
  chart.data.datasets[0].data = nonHiddenIndexes.map(i => data[i]);
  chart.data.datasets[0].backgroundColor = nonHiddenIndexes.map(i => backgroundColor[i]);
  chart.data.datasets[0].borderColor = nonHiddenIndexes.map(i => borderColor[i]);
  const legendLabelSpan = document.getElementById("legend-label-" + i);
  legendLabelSpan.style.textDecoration = hidden ? 'line-through' : '';
  chart.update();
};

const chart = new Chart("chart", {
  type: "bar",
  data: {
    labels: labels,
    datasets: [{
      label: "My First Dataset",
      data: [65, 59, 80, 81, 56, 55, 40],
      backgroundColor: ["rgba(255, 99, 132, 0.2)", "rgba(255, 159, 64, 0.2)", "rgba(255, 205, 86, 0.2)", "rgba(75, 192, 192, 0.2)", "rgba(54, 162, 235, 0.2)", "rgba(153, 102, 255, 0.2)", "rgba(201, 203, 207, 0.2)"],
      borderColor: ["rgb(255, 99, 132)", "rgb(255, 159, 64)", "rgb(255, 205, 86)", "rgb(75, 192, 192)", "rgb(54, 162, 235)", "rgb(153, 102, 255)", "rgb(201, 203, 207)"],
      borderWidth: 1
    }]
  },
  options: {
    legend: {
      display: false
    },
    legendCallback: chart => {
      let html = '<ul>';
      chart.data.labels.forEach((l, i) => {
        const ds = chart.data.datasets[0];
        const bgColor = ds.backgroundColor[i];
        const border = ds.borderWidth + 'px solid ' + ds.borderColor[i];
        html += '<li>' +
          '<span style="width: 36px; height: 14px; background-color:' + bgColor + '; border:' + border + '" onclick="onLegendClicked(event, \'' + i + '\')">&nbsp;</span>' +
          '<span id="legend-label-' + i + '" onclick="onLegendClicked(event, \'' + i + '\')">' +
          (Array.isArray(l) ? l.join('<br/>') : l) + '</span>' +
          '</li>';
      });
      return html + '</ul>';
    },
    scales: {
      yAxes: [{
        ticks: {
          beginAtZero: true
        }
      }]
    }
  }
});
document.getElementById("legend").innerHTML = chart.generateLegend();
#legend>ul {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
}

#legend li {
  cursor: pointer;
  margin: 10px 10px;
  display: flex;
}

#legend li span {
  padding-left: 8px;
  font-family: Arial, Helvetica, sans-serif;
  font-size: 12px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.9.3/Chart.min.js"></script>
<div style="width: 500px">
  <div id="legend"></div>
  <canvas id="chart" height="150"></canvas>
</div>

1πŸ‘

As you already mention in the title of your question, each bar must be defined in a separate dataset. Also data.labels should be defined as an array that contains a single empty string ([""]).

Please take a look at below runnable code and see how it works.

new Chart("chart", {
  type: "bar",
  data: {
    labels: [""],    
    datasets: [{
      label: "A",
      data: [65],
      backgroundColor: "rgba(255, 99, 132, 0.2)",
      borderColor: "rgb(255, 99, 132)",
      borderWidth: 1
    },
    {
      label: "B",
      data: [59],
      backgroundColor: "rgba(255, 159, 64, 0.2)",
      borderColor: "rgb(255, 159, 64)",
      borderWidth: 1
    },
    {
      label: "C",
      data: [80],
      backgroundColor: "rgba(255, 205, 86, 0.2)",
      borderColor: "rgb(255, 205, 86)",
      borderWidth: 1
    },
    {
      label: "D",
      data: [56],
      backgroundColor: "rgba(75, 192, 192, 0.2)",
      borderColor: "rgb(75, 192, 192)",
      borderWidth: 1
    },
    {
      label: "E",
      data: [55],
      backgroundColor: "rgba(54, 162, 235, 0.2)",
      borderColor: "rgb(54, 162, 235)",
      borderWidth: 1
    }]
  },
  options: {
    scales: {
      yAxes: [{
        ticks: {
          beginAtZero: true
        }
      }]
    }
  }
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.9.3/Chart.min.js"></script>
<canvas id="chart" height="80"></canvas>

UPDATE (13. May 2021)

Meanwhile I answered a similar question and improved the code I posted here. This solution is cleaner and now also displays the tick labels on the x-axis.

Leave a comment