[Chartjs]-Different Line Chart border colors for positive and negative values using Chart Js

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 segments 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.

Leave a comment