Reputation: 1374
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
but I would like to apply that same green-red gradient in "profit"'s legend box which is actually gray.
Upvotes: 1
Views: 802
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