Search code examples
chart.js

Strikethrough on label text not working when using generateLabel


The default functionality of label item text having a strikethrough applied when clicking on it from within the legend is not working. I would like to fix this issue so the strikethrough is applied (or toggled off) when clicked and the corresponding data is hidden from or shown to the chart.

const outsourcedProductionNames = ["Development", "Content", "Design"];
const outsourcedProductionCounts = new Array(outsourcedProductionNames.length).fill(0);

      // Function to truncate text and add ellipses
      function truncateText(text, maxLength) {
          if (text.length > maxLength) {
              return text.substring(0, maxLength) + '...';
          }
          return text;
      }

      // Function to generate distinct colors
      function generateDistinctColors(count) {
          const colors = [];
          for (let i = 0; i < count; i++) {
              const hue = i * (360 / count);
              colors.push(`hsl(${hue}, 100%, 75%)`);
          }
          return colors;
      }

      // Generate distinct colors for the chart
      const colors = generateDistinctColors(outsourcedProductionNames.length);

      const ctx = document.getElementById('allChartsOutsourcedProduction').getContext('2d');
      new Chart(ctx, {
          type: 'pie',
          data: {
              labels: outsourcedProductionNames,
              datasets: [{
                  label: 'Outsourced Production',
                  data: outsourcedProductionCounts,
                  backgroundColor: colors,
                  borderColor: 'white',
                  borderWidth: 2
              }]
          },
          options: {
              aspectRatio: 1.5,
              plugins: {
                  legend: {
                      labels: {
                          filter: (legendItem, data) => data.datasets[0].data[legendItem.index] != 0,
                          generateLabels: function(chart) {
                              return chart.data.labels.map((label, index) => {
                                  return {
                                      text: truncateText(label, 20),
                                      fillStyle: chart.data.datasets[0].backgroundColor[index],
                                      strokeStyle: undefined, // Remove the border color
                                      lineWidth: 0, // Remove the border width
                                      hidden: chart.getDatasetMeta(0).data[index].hidden,
                                      index: index
                                  };
                              });
                          }
                      }
                  },
                  tooltip: {
                      titleFont: {
                          family: "sans-serif",
                          weight: 900
                      },
                      callbacks: {
                          label: data => (' ' + data.parsed + ' Projects')
                      }
                  }
              }
          }
      });

I have tried making a separate plugin for the strikethrough function. This worked to get the strike through, but it caused my tooltip styling, truncate function, and filter (having items not show in legend that have 0 results) to not work.


Solution

  • It took me a lot of debugging to realize that the problem in your code is just at value of the hidden parameter.

    The function generateLabels is called not only at the initialization of the chart (as its name might suggest), but also each time an item's visibility is toggled. However, after the click on a legend item, the value of chart.getDatasetMeta(0).data[index].hidden that you use is not changed, it will stay false or undefined that's why you should use !chart.getDataVisibility(index) as it is done in the source code, which, by the looks of it you have already consulted.

    In short, the only replacement required is

    ......
       hidden: !chart.getDataVisibility(index),
    ......   
    

    Snippet, that also logs each time getDataVisibility is called:

    const outsourcedProductionNames = ["Development", "Content", "Design"];
    const outsourcedProductionCounts = new Array(outsourcedProductionNames.length).fill(1);
    
    // Function to truncate text and add ellipses
    function truncateText(text, maxLength) {
        if (text.length > maxLength) {
            return text.substring(0, maxLength) + '...';
        }
        return text;
    }
    
    // Function to generate distinct colors
    function generateDistinctColors(count) {
        const colors = [];
        for (let i = 0; i < count; i++) {
            const hue = i * (360 / count);
            colors.push(`hsl(${hue}, 100%, 75%)`);
        }
        return colors;
    }
    
    // Generate distinct colors for the chart
    const colors = generateDistinctColors(outsourcedProductionNames.length);
    
    
    let logEnabled = false;
    const ctx = document.getElementById('allChartsOutsourcedProduction').getContext('2d');
    new Chart(ctx, {
        type: 'pie',
        data: {
            labels: outsourcedProductionNames,
            datasets: [{
                label: 'Outsourced Production',
                data: outsourcedProductionCounts,
                 backgroundColor: colors,
                 borderColor: 'white',
                 borderWidth: 2
            }]
        },
        options: {
            aspectRatio: 1.5,
            plugins: {
                legend: {
                    labels: {
                        filter: (legendItem, data) => data.datasets[0].data[legendItem.index] != 0,
                        generateLabels: function(chart) {
                            if(logEnabled) console.log('generateLabels called');
                            return chart.data.labels.map((label, index) => {
                                return {
                                    text: truncateText(label, 20),
                                    fillStyle: chart.data.datasets[0].backgroundColor[index],
                                    strokeStyle: undefined, // Remove the border color
                                    lineWidth: 0, // Remove the border width
                                    hidden: !chart.getDataVisibility(index),
                                    index: index
                                };
                            });
                        }
                    }
                },
                tooltip: {
                    titleFont: {
                        family: "sans-serif",
                        weight: 900
                    },
                    callbacks: {
                        label: data => (' ' + data.parsed + ' Projects')
                    }
                }
            }
        }
    });
    setTimeout(()=>{logEnabled = true;}, 1000); // log only after the chart is built and animation over
    <div style="min-height: 60vh">
        <canvas id="allChartsOutsourcedProduction">
        </canvas>
    </div>
    <script src="https://cdn.jsdelivr.net/npm/chart.js"></script>