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 + '\')"> </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 redefinesdata.labels
and thedataset
(data
,backgroundColor
andborderColor
) 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 + '\')"> </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.