hldev
hldev

Reputation: 1374

How to set gradient in legend box color in Chart,js

I am trying to make a profit chart with different colors for profit and losses, so far I extended default line chart with

Chart.defaults.ProfitLossLine = Chart.helpers.clone(Chart.defaults.line);
Chart.controllers.ProfitLossLine = Chart.controllers.line.extend({
    update: function () {
        // get the min and max values
        const dataset = this.chart.data.datasets[0];
        const min = Math.min.apply(null, dataset.data);
        const max = Math.max.apply(null, dataset.data);
        const yScale = this.getScaleForId(this.getMeta(0).yAxisID);
        // figure out the pixels for these and the value 0
        const top = yScale.getPixelForValue(max);
        const zero = yScale.getPixelForValue(0);
        const bottom = yScale.getPixelForValue(min);
        // build a gradient that switches color at the 0 point
        const ctx = this.chart.chart.ctx;
        const gradient = ctx.createLinearGradient(0, top, 0, bottom);
        const ratio = Math.min((zero - top) / (bottom - top), 1);
        gradient.addColorStop(0, dataset.positiveBorderColor);
        gradient.addColorStop(ratio, dataset.positiveBackgroundColor);
        gradient.addColorStop(ratio, dataset.negativeBackgroundColor);
        gradient.addColorStop(1, dataset.negativeBorderColor);
        dataset.borderColor = gradient;
        dataset.backgroundColor = gradient;
        dataset.pointBorderColor = gradient;
        dataset.pointBackgroundColor = gradient;
        dataset.pointHoverBorderColor = gradient;
        dataset.pointHoverBackgroundColor = gradient;
        this.chart.options.legend.labels.generateLabels = (chart) => {
            const labels = Chart.defaults.global.legend.labels.generateLabels(chart);
            for (const key in labels) {
                labels[key].fillStyle  = gradient;
                labels[key].strokeStyle = gradient; 
              }
            return labels;
        }
        return Chart.controllers.line.prototype.update.apply(this, arguments);
    }
});

and got

enter image description here

but I would like to apply that same green-red gradient in "profit"'s legend box which is actually gray.

Upvotes: 1

Views: 802

Answers (1)

Konrad Staniszewski
Konrad Staniszewski

Reputation: 11

Take a look at this: Chart.js Legend Item interface

You can achieve this like so:

let maxLeft = 0;
let maxWidth = 0;
function generateLabelsNew(chart) {
    const datasets = chart.data.datasets;
    const {labels: {usePointStyle, pointStyle, textAlign, color}} = chart.legend.options;
    
    return chart._getSortedDatasetMetas().map((meta) => {
        const style = meta.controller.getStyle(usePointStyle ? 0 : undefined);
        const borderWidth = style.borderWidth;
        
        const box = meta?.controller?.chart?.legend?.legendHitBoxes[meta.index];

        if (box?.left > maxLeft) {
            maxLeft = box.left;
        }
        if (box?.width > maxWidth) {
            maxWidth = box.width;
        }
        const gradient = chart.ctx.createLinearGradient(maxLeft, 0, maxLeft + maxWidth, 0);
        gradient.addColorStop(0, 'green');
        gradient.addColorStop(1, 'red');
        
        return {
            text: datasets[meta.index].label,
            fillStyle: gradient,//style.backgroundColor,
            fontColor: color,
            hidden: !meta.visible,
            lineCap: style.borderCapStyle,
            lineDash: style.borderDash,
            lineDashOffset: style.borderDashOffset,
            lineJoin: style.borderJoinStyle,
            lineWidth: (borderWidth.width + borderWidth.height) / 4,
            strokeStyle: style.borderColor,
            pointStyle: pointStyle || style.pointStyle,
            rotation: style.rotation,
            textAlign: textAlign || style.textAlign,
            borderRadius: 0,
            datasetIndex: meta.index
        };
    }, this);
}

Then, in your config you need to set the generateLabel function to the one you just generated like so:

const options = {
    plugins: {
        legend: {
            labels: {
                generateLabels: generateLabelsNew,
            },
        },
    },
};

This is effectively over-riding the default label generation function and setting the fill style for the box to a gradient. You use the maxWidth and maxLeft to get the positions of the bounding box of each legend item.

Upvotes: 0

Related Questions