Chartjs-Why am I getting the wrong Background Color when applying multiple colors to data points?

1👍

As described in the documentation here you can specify the fill as an object instead of setting it to true in which you can tell chart.js to fill above a specific value with a different collor as below.

var ctx = document.getElementById("chart").getContext("2d");
var myLine = new Chart(ctx, {
  type: 'line',
  data: {
    labels: ["label1", "label2", "label3", "label4"],
    datasets: [{
      label: 'Demo',
      backgroundColor: function(context) {
        const index = context.dataIndex;
        const value = context.dataset.data[index];
        return value < 0 ? 'rgba(54, 162, 235, 1)' : 'rgba(53, 122, 135, 1)';
      },
      fill: {
        target: {
          value: 0
        },
        below: 'rgba(54, 162, 235, 1)',
        above: 'rgba(53, 122, 135, 1)'
      },
      data: [-2, -3, 4, 6],
    }]
  },
  options: {},
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/4.2.0/chart.umd.js"></script>
<canvas id="chart"></canvas>

-1👍

Thanks to jordanwillis. His codepen has helped me a lot to solve this problem. Of course, solving this problem is done manually with an explanation as in the code below.

// decimal rounding algorithm
// see: https://plnkr.co/edit/uau8BlS1cqbvWPCHJeOy?p=preview
var roundNumber = function (num, scale) {
  var number = Math.round(num * Math.pow(10, scale)) / Math.pow(10, scale);
  if(num - number > 0) {
    return (number + Math.floor(2 * Math.round((num - number) * Math.pow(10, (scale + 1))) / 10) / Math.pow(10, scale));
  } else {
    return number;
  }
};

// save the original line element so we can still call it's 
// draw method after we build the linear gradient
var origLineElement = Chart.elements.Line;

// define a new line draw method so that we can build a linear gradient
// based on the position of each point
Chart.elements.Line = Chart.Element.extend({
  draw: function() {
    var vm = this._view;
    var backgroundColors = this._chart.controller.data.datasets[this._datasetIndex].backgroundColor;
    var points = this._children;
    var ctx = this._chart.ctx;
    var minX = points[0]._model.x;
    var maxX = points[points.length - 1]._model.x;
    var linearGradient = ctx.createLinearGradient(minX, 0, maxX, 0);

    // iterate over each point to build the gradient
    points.forEach(function(point, i) {
      // `addColorStop` expects a number between 0 and 1, so we
      // have to normalize the x position of each point between 0 and 1
      // and round to make sure the positioning isn't too percise 
      // (otherwise it won't line up with the point position)
      var colorStopPosition = roundNumber((point._model.x - minX) / (maxX - minX), 2);

      // special case for the first color stop
      if (i === 0) {
        linearGradient.addColorStop(0, backgroundColors[i]);
      } else {
        // only add a color stop if the color is different
        if (backgroundColors[i] !== backgroundColors[i-1]) {
          // add a color stop for the prev color and for the new color at the same location
          // this gives a solid color gradient instead of a gradient that fades to the next color
          linearGradient.addColorStop(colorStopPosition, backgroundColors[i - 1]);
          linearGradient.addColorStop(colorStopPosition, backgroundColors[i]);
        }
      }
    });

    // save the linear gradient in background color property
    // since this is what is used for ctx.fillStyle when the fill is rendered
    vm.backgroundColor = linearGradient;

    // now draw the lines (using the original draw method)
    origLineElement.prototype.draw.apply(this);
  }               
});

// we have to overwrite the datasetElementType property in the line controller
// because it is set before we can extend the line element (this ensures that 
// the line element used by the chart is the one that we extended above)
Chart.controllers.line = Chart.controllers.line.extend({
  datasetElementType: Chart.elements.Line,
});

// the labels used by the chart
var labels = ["Year 1", "Year 2", "Year 3", "Year 4", "Year 5"];

// the line chart point data
var lineData = [-50, -25, -6.04, 24.98, 50];

// colors used as the point background colors as well as the fill colors
var fillColors = [];
lineData.forEach((l)=> {
  fillColors.push(l < 0 ? 'rgba(54, 162, 235, 1)' : 'rgba(53, 122, 135, 1)');
});

// get the canvas context and draw the chart
var ctx = document.getElementById("solarPaybackChart").getContext("2d");
var myLine = new Chart(ctx, {
  type: 'line',
  data: {
    labels: labels,
    datasets: [{
      label: '',
      backgroundColor: fillColors, // now we can pass in an array of colors (before it was only 1 color)
      pointBackgroundColor: fillColors,
      fill: true,
      data: lineData,
    }]
  },
  options: {
    tooltips: {
        callbacks: {
            label: function (context) {
                let label = '';

                if (context.yLabel !== null) {
                    label += context.yLabel + '%';
                }

                return label;
            }
        }
    },
    scales: {
        x: {
            display: false,
            maxTicks: 15,
            grid: {
                display: false
            },
            title: {
                display: true,
                text: 'Year'
            }
        },
        y: {
            display: false,
            maxTicksLimit: 5,
            grid: {
                display: false
            },
            title: {
                display: false,
                text: 'Return'
            }
        }
    },
    plugins: {
        legend: {
            display: false
        },
        tooltips: {
            callbacks: {
                label: function (context) {
                    let label = '';

                    if (context.yLabel !== null) {
                        label += context.yLabel + '%';
                    }

                    return label;
                }
            }
        },
    },
    interaction: {
        intersect: false,
        mode: 'index',
    },
    spanGaps: true,
  }
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.5.0/Chart.js"></script>
<canvas id="solarPaybackChart"></canvas>

The above code is implemented with Chart.js 2.x. I wonder how this is implemented in Chart.js 3.x

Leave a comment