1👍
The solution for changing the color of the line depending on the sign of the points was already implemented by setting the borderColor
for segment
s and it worked fine for all segments that had both ends of the same sign; the config object was
datasets:[{
data: ....
.......
segment: {
borderColor: (ctx) => {
return getBorderColor(ctx);
}
},
}]
with the actual computation implemented in the function getBorderColor
(slightly modified from the original to simplify the introduction of my extension below):
const getBorderColor =
(ctx, colPositive = "green", colNegative = "red") => {
return ctx.p0.parsed.y >= 0 ? colPositive : colNegative;
};
To adapt this to work for segments that intersect the x axis, the solution I found was to set the color as a LinearGradient
with four color stops described by this table:
point: p0 x-axis intersection p1
coordinates: (x0, y0) (_, 0) (x1, y1)
fraction: 0 frac 1
color: green ------> green|red --------> red
where frac
is easy to compute from the coordinates of the two points. One can see this is not actually a visual gradient, the change of color from "green" to "red" (used as an example) is done at the same point, the intersection of the segment to the x axis.
Here’s the code with explanations in comments:
const getBorderColor = (ctx, colPositive = "green", colNegative = "red") => {
if (ctx.p0.parsed.y * ctx.p1.parsed.y < 0) {
// if the segment changes sign from p0 to p1
const x0 = ctx.p0.parsed.x,
x1 = ctx.p1.parsed.x,
y0 = ctx.p0.parsed.y,
y1 = ctx.p1.parsed.y,
dataset = ctx.chart.data.datasets[ctx.datasetIndex],
//identify the correct axes used for the dataset
xAxisId = dataset.xAxisId ?? "x",
yAxisId = dataset.yAxisId ?? "y",
//transform values to pixels
x0px = ctx.chart.scales[xAxisId].getPixelForValue(x0),
x1px = ctx.chart.scales[xAxisId].getPixelForValue(x1),
y0px = ctx.chart.scales[yAxisId].getPixelForValue(y0),
y1px = ctx.chart.scales[yAxisId].getPixelForValue(y1);
// create gradient form p0 to p1
const gradient = ctx.chart.ctx.createLinearGradient(x0px, y0px, x1px, y1px);
// calculate frac - the relative length of the portion of the segment
// from p0 to the point where the segment intersects the x axis
const frac = Math.abs(y0) / (Math.abs(y0) + Math.abs(y1));
// set colors at the ends of the segment
const [col_p0, col_p1] =
y0 > 0 ? [colPositive, colNegative] : [colNegative, colPositive];
gradient.addColorStop(0, col_p0);
gradient.addColorStop(frac, col_p0);
gradient.addColorStop(frac, col_p1);
gradient.addColorStop(1, col_p1);
return gradient;
}
return ctx.p0.parsed.y >= 0 ? colPositive : colNegative;
};
and the fork of the original codesandbox.