DEV Community

Cover image for Creating a custom Chart.js legend style
Gisela Miranda Difini
Gisela Miranda Difini

Posted on • Originally published at giselamirandadifini.com

Creating a custom Chart.js legend style

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:

Chart.js example

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]}">
                        &nbsp;&nbsp;&nbsp;&nbsp;
                      </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>`;
      },
Enter fullscreen mode Exit fullscreen mode

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:

Chart.js custom legend style

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.

Chartjs legend gif

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)
    );
  });
Enter fullscreen mode Exit fullscreen mode

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();
  };
Enter fullscreen mode Exit fullscreen mode

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();
  };
};

Enter fullscreen mode Exit fullscreen mode

And now we are able to click and have those chart.js animations:

Chartjs legend animation gif

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 😄

Edit chartjs-legend-styling

See you in the next post! Hope you enjoy it.

Discussion (2)

Collapse
leomjaques profile image
leonardo jaques 👨🏻‍💻

Oh wow, this is so pretty! I'm sure to give it a try. Keep posting <3

Collapse
dimagashko profile image
Dmitry Gashko

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.