chartjs v4 legend text not updating when using afterLayout

42 views Asked by At

I am using chartjs v4. I am modifying the legend text. I see code gets executed but the legend text does not change. Here is the code:

var myChart = new Chart(ctx, {
    type: 'bar',
    plugins: [{
        afterLayout: chart => {
            chart.legend.legendItems.forEach(
                (label) => {
                    label.text += ' Test';
                    return label;
                }
            )
        }
    }],

...

The legend labels remain the same (It does not add "Test" at the end of each label). What am I missing? Something is not being refreshed somehow.

UPDATE/SOLUTION

First I used afterDraw and that worked. But Kikon's solution was even better. I noticed that afterDraw was executing twice and therefore updating the legend labels twice unnecessarily. I am going to use Kikon's solution and use afterUpdate instead of afterDraw:

var myChart = new Chart(ctx, {
        type: 'bar',
        plugins: [{
            afterUpdate: (chart, args, opts) => {
                chart.legend.legendItems.forEach(
                    (label) => {
                        label.text += ' Test ';
                        return label;
                    }
                )
            }
        }],

...

2

There are 2 answers

4
kikon On BEST ANSWER

The labels are re-built in the legend plugin's afterUpdate (source code), which is called later than afterLayout; so, there are two possibilities:

  • set your plugin code to change the labels in afterUpdate rather than in afterLayout
  • [probably unnecessarily hacky] remove the legend plugin's afterUpdate to avoid the redundant legend items recompute:
    Chart.registry.getPlugin('legend').afterUpdate = () => null;
    

As discussed with Robert Smith in comments, afterUpdate (from the first solution above) is the earliest called plugin hook that can be used to change the label texts. Any of the render process hooks (from beforeRender to afterRender) could also be used.

I'd add here, for reference, that there is another method to adjust the legend labels dynamically, which is to customize the generateLabels function of the options.plugins.legend.labels namespace. The difficulty with it is that it should return several options for the legend item (see Legend Item Interface) that one is not concerned with here. To avoid that, one might call the default generateLabels function first:

const config = {
    //..... data, other configuration items
    options: {
        // .... other options
        plugins: {
            legend: {
                labels: {
                    generateLabels(chart){
                        // default labels
                        const labels = chart.registry.getPlugin("legend")
                            .defaults.labels.generateLabels(chart);
                        
                        labels.forEach(
                            label => {
                                if(!label.text.match(/ Test$/)){ 
                                // avoid applying the change multiple times
                                    label.text += ' Test';
                                }
                            }
                        );
                        return labels;
                    }
                }
            }
       }
    }
}
0
Robert Smith On

Solution found! The solution is to use afterDraw instead of afterLayout as follows:

var myChart = new Chart(ctx, {
            type: 'bar',
            plugins: [{
                afterDraw: (chart, args, opts) => {
                    chart.legend.legendItems.forEach(
                        (label) => {
                            label.text += ' Test ';
                            return label;
                        }
                    )
                }
            }],
...

Now the legend text successfully updates!

UPDATE

Kikon's solution was even better. I noticed that afterDraw was executing twice and therefore updating the legend labels twice unnecessarily. I am going to use Kikon's solution and use afterUpdate instead of afterDraw:

var myChart = new Chart(ctx, {
            type: 'bar',
            plugins: [{
                afterUpdate: (chart, args, opts) => {
                    chart.legend.legendItems.forEach(
                        (label) => {
                            label.text += ' Test ';
                            return label;
                        }
                    )
                }
            }],
...