vue.jschart.jschart.js2vue-chartjs

chart.js line segment coloring


in chart js (4.4.8)

i want to give discrete line colors based on a baseline and threshold value of the data point.

  1. when i give line segment colors it appies color to whole line segment and looks like this enter image description here

when i give

   ctx.createLinearGradient(0, yBaselinePixel, 0, yThresholdPixel);

it looks like this enter image description here

  1. i want to apply colors to only those part of the line segment(not on whole line segment) which crosses baseline or threshold as following

enter image description here

example example reference link

i'm using chart.js 4.4.8 in vue.js 4.2.5


const getGradient = (ctx, chartArea, chart, yBaselinePixel, yThresholdPixel) => {
  const normalize = (pixel) => (pixel - chartArea.bottom) / (chartArea.top - chartArea.bottom);
  const stopBaseline = normalize(yBaselinePixel);
  const stopThreshold = normalize(yThresholdPixel);

  const gradient = ctx.createLinearGradient(0, yBaselinePixel, 0, yThresholdPixel);
  gradient.addColorStop(0, "#22c55e");
  gradient.addColorStop(stopBaseline, "#f9ba4f");
  gradient.addColorStop(stopThreshold, "#f59e0b");
  gradient.addColorStop(1, "#ef4444");

  return gradient;
}
let flushRateGradient;
const flushRateChartData = {
  labels: res.data.map(d => d.build_no),
  datasets: [
    {
      label: 'flush rate',
      data: new Array(res.data.length).fill(0),
      pointRadius: 2,
      // borderColor: '#bd7ebe99',
      legendColor: '#bd7ebe99',
      tension: 0.2,
      isShown: true,
      borderColor: (context) => { // the gradient approach
        const chart = context.chart;
        const { ctx, chartArea } = chart;

        if (!chartArea) { // on initial chart load
          return;
        }
        if (flushRateGradient) {
          return flushRateGradient;
        }
        const yBaselinePixel = chart.scales.y.getPixelForValue(flushRateBaselineValue);
        const yThresholdPixel = chart.scales.y.getPixelForValue(flushRateThresholdValue);
        flushRateGradient = getGradient(ctx, chartArea, chart, yBaselinePixel, yThresholdPixel);
        return flushRateGradient;
      },
      /* ????? 
      HOW TO ADD MULTIPLE COLOR STOP HERE IN SEGMENT APPROACH WITHOUT APPLYING GRADIENT.
      AS THIS IS COLORING THE WHOLE LINE SEGMENT WITH ONE COLOR
      ????? */
      // segment: { // the segment approach
      //   borderColor: ctx => (ctx.p1.parsed.y > flushRateThresholdValue) ? 'red' : '#bd7ebe99',
      // }
    },
    {
      label: '', // actual threshold drawn on chart
      type: 'line',
      data: new Array(res.data.length).fill(flushRateBaselineValue),
      pointRadius: 0,
      borderColor: '#ea6e6e',
      borderDash: [4, 7],
      borderWidth: 2,
      tension: 0.2,
      isShown: true,
    },
    {
      label: '', // actual baseline drawn on chart
      type: 'line',
      data: new Array(res.data.length).fill(flushRateBaselineValue),
      pointRadius: 0,
      borderColor: '#f9ba4f',
      borderDash: [4, 7],
      borderWidth: 2,
      tension: 0.2,
      isShown: true,
    },
  ],
}

Solution

  • I can't fully verify your code, as you haven't included data and other parameters like flushRateBaselineValue and flushRateBaselineValue.

    But if the stopBaseLine and stopThreshold values are computed correctly, and you want to use color "#22c55e" from 0 to stopBaseLine, color "#f9ba4f" from stopBaseLine to stopThereshold and color #ef4444 from stopThereshold to 1, you have to set the gradient this way:

    const gradient = ctx.createLinearGradient(0, chartArea.bottom, 0, chartArea.top);
    
    gradient.addColorStop(0, "#22c55e");
    gradient.addColorStop(stopBaseline, "#22c55e");
    
    gradient.addColorStop(stopBaseline, "#f9ba4f");
    gradient.addColorStop(stopThreshold, "#f9ba4f");
    
    gradient.addColorStop(stopThreshold, "#ef4444");
    gradient.addColorStop(1, "#ef4444");
    

    The arguments of the function ctx.createLinearGradient are pixel positions of the full extent of the gradient.

    And, if the color is to be the same on a fragment of the gradient - the fractional 0 to 1 positions of the color stops, should repeat that color for the start and end of that interval (as the linear interpolation between two equal values is constant -- the same value on the whole interval).

    Here are those changes applied to a (debuggable) snippet, based on a standard example from chart.js docs:

    const getGradient = (ctx, chartArea, chart, yBaselinePixel, yThresholdPixel) => {
        const normalize = (pixel) => (pixel - chartArea.bottom) / (chartArea.top - chartArea.bottom);
        const stopBaseline = normalize(yBaselinePixel);
        const stopThreshold = normalize(yThresholdPixel);
    
        const gradient = ctx.createLinearGradient(0, chartArea.bottom, 0, chartArea.top);
    
        gradient.addColorStop(0, "#22c55e");
        gradient.addColorStop(stopBaseline, "#22c55e");
    
        gradient.addColorStop(stopBaseline, "#f9ba4f");
        gradient.addColorStop(stopThreshold, "#f9ba4f");
    
        gradient.addColorStop(stopThreshold, "#ef4444");
        gradient.addColorStop(1, "#ef4444");
    
        return gradient;
    }
    
    let flushRateGradient;
    const flushRateBaselineValue = 5;
    const flushRateThresholdValue = 7;
    const config = {
        type: 'line',
        data: {
            labels: ["January", "February", "March", "April", "May", "June", "July"],
            datasets: [{
                label: "Dataset 1",
                data: [
                    10, 5, 2, 10, 4, 1, 10
                ],
                tension: 0.2,
                borderColor: (context) => { // the gradient approach
                    const chart = context.chart;
                    const { ctx, chartArea } = chart;
    
                    if (!chartArea) { // on initial chart load
                        return;
                    }
                    if (flushRateGradient) {
                        return flushRateGradient;
                    }
                    const yBaselinePixel = chart.scales.y.getPixelForValue(flushRateBaselineValue);
                    const yThresholdPixel = chart.scales.y.getPixelForValue(flushRateThresholdValue);
                    flushRateGradient = getGradient(ctx, chartArea, chart, yBaselinePixel, yThresholdPixel);
                    return flushRateGradient;
                },
            }]
        },
        options: {
            responsive: true,
            maintainAspectRatio: false,
            title:{
                display:true,
                text:'Chart.js Line Chart'
            },
            tooltips: {
                mode: 'index',
                intersect: false,
            },
            hover: {
                mode: 'nearest',
                intersect: true
            }
        }
    };
    
    const chart = new Chart("canvas", config);
    <div id="container" style="height: 300px;">
        <canvas id="canvas"></canvas>
    </div>
    
    <script src="https://cdn.jsdelivr.net/npm/chart.js"></script>