For those who don't know chart.js, it's a javascript
chart library.
Using a library for creating data visualization can be a little painful when you want something beyond the examples and styles provided by those libraries.
I've decided creating this post when I spent a lot of effort to customize a doughnut chart style, cause I needed to use a custom legend style for that chart.
This is what you can create without any custom styling:
So going deep into the documentation, there is a legendCallback
option that enables us to insert a HTML legend to the chart and this will be rendered once we call generateLegend()
function from chart.js.
This is what my legendCallback looks like:
legendCallback: (chart) => {
const renderLabels = (chart) => {
const { data } = chart;
return data.datasets[0].data
.map(
(_, i) =>
`<li>
<div id="legend-${i}-item" class="legend-item">
<span style="background-color:
${data.datasets[0].backgroundColor[i]}">
</span>
${
data.labels[i] &&
`<span class="label">${data.labels[i]}: $${data.datasets[0].data[i]}</span>`
}
</div>
</li>
`
)
.join("");
};
return `
<ul class="chartjs-legend">
${renderLabels(chart)}
</ul>`;
},
Here I'm mapping through all elements in the dataset and getting it's background color and label (previously defined inside the charts options object).
With this HTML + some CSS I can generate something like this:
YES! JOB DONE!
... actually not quite ;)
WHAT?
yup, until this point we have the legend style but if we click on it, nothing happens on the chart... we don't have that excluding data animation as if we were using the default legend.
We need to create click event listeners for each legend:
const legendItemSelector = ".legend-item";
const labelSeletor = ".label";
const legendItems = [
...containerElement.querySelectorAll(legendItemSelector)
];
legendItems.forEach((item, i) => {
item.addEventListener("click", (e) =>
updateDataset(e.target.parentNode, i)
);
});
And then based on the current state of the data (available in this getDatasetMeta
function) from the legend you clicked, you can hide and show that data in the chart:
const updateDataset = (currentEl, index) => {
const meta = myChart.getDatasetMeta(0);
const labelEl = currentEl.querySelector(labelSeletor);
const result = meta.data[index].hidden === true ? false : true;
if (result === true) {
meta.data[index].hidden = true;
labelEl.style.textDecoration = "line-through";
} else {
labelEl.style.textDecoration = "none";
meta.data[index].hidden = false;
}
myChart.update();
};
Together they look like this:
export const bindChartEvents = (myChart, containerElement) => {
const legendItemSelector = ".legend-item";
const labelSeletor = ".label";
const legendItems = [
...containerElement.querySelectorAll(legendItemSelector)
];
legendItems.forEach((item, i) => {
item.addEventListener("click", (e) =>
updateDataset(e.target.parentNode, i)
);
});
const updateDataset = (currentEl, index) => {
const meta = myChart.getDatasetMeta(0);
const labelEl = currentEl.querySelector(labelSeletor);
const result = meta.data[index].hidden === true ? false : true;
if (result === true) {
meta.data[index].hidden = true;
labelEl.style.textDecoration = "line-through";
} else {
labelEl.style.textDecoration = "none";
meta.data[index].hidden = false;
}
myChart.update();
};
};
And now we are able to click and have those chart.js animations:
This post is more focused on the custom styling so if you are curious about how to create a chart.js chart and make that work, here is the example that you can take a look 😄
See you in the next post! Hope you enjoy it.
Top comments (2)
Oh wow, this is so pretty! I'm sure to give it a try. Keep posting <3
Great, but I just want to notice that "es6 templates" doesn't provide any XSS protections, so in this case if the user can affect on the labels an XSS would be possible.