javascriptangularjschart.jsangular-chart

Angular-Chart-JS - Line chart with different fill colors according to points' range


I'm using Angular-Chart-js for my website to display some types of graphs, one of them is a line chart.

I would like the line chart to be color-filled, but with different colors according to the y-axis' value. Like in this photo: enter image description here

I've tried to have different data arrays in the "data" array of the graph, the first one has all the values, the second one has all but the ones painted in green (on the right), the third is the same array only until the purple range etc. and then have for each dataset its own color, but eventually I get a graph with a single color according to the last dataset color.

What am I missing? Is there any way to accomplish that?

Thanks.


Solution

  • Unfortunately, you cannot achieve this with the current chart.js configuration options. The reason is because the line chart backgroundColor option (the option that controls the color of the line chart fill) only accepts a single value.

    After digging through the current chart.js 2.5 source, I found that it is possible to extend the line element's draw() method and force chart.js to use a canvas linear gradient for the fill (instead of just a single color). With a little bit of math, we can convert the x position of each point into a linear gradient color stop position and build a gradient.

    With this enhancement, you can now pass in an array of colors to the line chart backgroundColor option to achieve varying colored fill regions. Here is an example of what a resulting chart would look like.

    Line Chart With Colored Fill Regions

    Here is how to actually do it (with a working example at the bottom)

    First, we must extend Chart.elements.Line and overwrite it's draw() method so that we can build the linear gradient based upon the position of each point, use it as the line fill, and then draw the line.

    // 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);
      }               
    });
    

    Then, we have to also extend the line chart to ensure that the line element used by the chart is the one that we extended above (since this property is already set at load time)

    // 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,
    });
    

    With this done, we can now pass in an array of colors to the line chart backgroundColor property (instead of just a single value) to control the line fill.

    Here is a codepen example that demonstrates all that has been discussed.

    Caveats: