[Chartjs]-Chart js different background for y axis

3👍

There is not a built in option, but we can achieve the result with some code.

var ctx = document.getElementById("chart").getContext("2d");

var scatterChart = new Chart(ctx, {
  type: "line",
  data: {
    datasets: [{
      label: " Dataset",
      data: [{
          x: 1,
          y: 10
        },
        {
          x: 2,
          y: 50
        },
        {
          x: 3,
          y: 88
        },
        {
          x: 4,
          y: 5
        }
      ]
    }]
  },
  options: {
    backgroundRules: [{
        backgroundColor: "red",
        yAxisSegement: 40
      },
      {
        backgroundColor: "yellow",
        yAxisSegement: 70
      },
      {
        backgroundColor: "green",
        yAxisSegement: Infinity
      }
    ],
    scales: {
      xAxes: [{
        type: "linear",
        position: "bottom"
      }],
      yAxes: [{
        color: ["#123456", "#234567"]
      }]
    }
  },
  plugins: [{
    beforeDraw: function(chart) {
      var ctx = chart.chart.ctx;
      var ruleIndex = 0;
      var rules = chart.chart.options.backgroundRules;
      var yaxis = chart.chart.scales["y-axis-0"];
      var xaxis = chart.chart.scales["x-axis-0"];
      var partPercentage = 1 / (yaxis.ticksAsNumbers.length - 1);
      for (var i = yaxis.ticksAsNumbers.length - 1; i > 0; i--) {
        if (yaxis.ticksAsNumbers[i] < rules[ruleIndex].yAxisSegement) {
          ctx.fillStyle = rules[ruleIndex].backgroundColor;
          ctx.fillRect(xaxis.left, yaxis.top + (i - 1) * (yaxis.height * partPercentage), xaxis.width, yaxis.height * partPercentage);
        } else {
          ruleIndex++;
          i++;
        }
      }
    }
  }]
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.9.3/Chart.bundle.js"></script>
<div class="container">
  <canvas id="chart"></canvas>
</div>

3👍

Well you can try something (ugly) like this, there is a comment in the snippet where the gradient is defined.

Surely the colors and composition of the gradient can be determined by input properties like you want. Also, one could play with the gradient position or make a radial gradient instead of a linear one.

One last thing, this snippet can really be improved, basically what I did is to identify how the chart is drawn by the library and in which part of it’s lifecycle is done, then I copy it into a plugin and replace the solid background color for a canvas linear gradient 😉

To improve the snippet I would start by trying to use the methods defined inside the Chart object (like lineTo() or drawArea()) instead of copying them inside the plugin, then implement the options defined inside the options object to create the linear gradient.

var ctx = document.getElementById("chart").getContext("2d");

var scatterChart = new Chart(ctx, {
  type: "line",
  data: {
    datasets: [{
      label: " Dataset",
      data: [{
          x: 1,
          y: 10
        },
        {
          x: 2,
          y: 50
        },
        {
          x: 3,
          y: 88
        },
        {
          x: 4,
          y: 5
        }
      ]
    }]
  },
  options: {
    scales: {
      xAxes: [{
        type: "linear",
        position: "bottom"
      }]
    }
  },
  plugins: [{
    beforeDatasetDraw: function(chart, options) {
      var metasets = chart._getSortedVisibleDatasetMetas();
      var ctx = chart.ctx;
      var meta, i, el, view, points, mapper, color;

      var clipArea = (ctx, area) => {
        ctx.save();
        ctx.beginPath();
        ctx.rect(area.left, area.top, area.right - area.left, area.bottom - area.top);
        ctx.clip();
      };

      var unclipArea = (ctx) => {
        ctx.restore();
      };

      var isDrawable = (point) => {
        return point && !point.skip;
      }

      var lineTo = (ctx, previous, target, flip) => {
        var stepped = target.steppedLine;
        if (stepped) {
          if (stepped === 'middle') {
            var midpoint = (previous.x + target.x) / 2.0;
            ctx.lineTo(midpoint, flip ? target.y : previous.y);
            ctx.lineTo(midpoint, flip ? previous.y : target.y);
          } else if ((stepped === 'after' && !flip) || (stepped !== 'after' && flip)) {
            ctx.lineTo(previous.x, target.y);
          } else {
            ctx.lineTo(target.x, previous.y);
          }
          ctx.lineTo(target.x, target.y);
          return;
        }

        if (!target.tension) {
          ctx.lineTo(target.x, target.y);
          return;
        }

        ctx.bezierCurveTo(
          flip ? previous.controlPointPreviousX : previous.controlPointNextX,
          flip ? previous.controlPointPreviousY : previous.controlPointNextY,
          flip ? target.controlPointNextX : target.controlPointPreviousX,
          flip ? target.controlPointNextY : target.controlPointPreviousY,
          target.x,
          target.y);
      }

      var drawArea = (ctx, curve0, curve1, len0, len1) => {
        var i, cx, cy, r;

        if (!len0 || !len1) {
          return;
        }

        // building first area curve (normal)
        ctx.moveTo(curve0[0].x, curve0[0].y);
        for (i = 1; i < len0; ++i) {
          lineTo(ctx, curve0[i - 1], curve0[i]);
        }

        if (curve1[0].angle !== undefined) {
          cx = curve1[0].cx;
          cy = curve1[0].cy;
          r = Math.sqrt(Math.pow(curve1[0].x - cx, 2) + Math.pow(curve1[0].y - cy, 2));
          for (i = len1 - 1; i > 0; --i) {
            ctx.arc(cx, cy, r, curve1[i].angle, curve1[i - 1].angle, true);
          }
          return;
        }

        // joining the two area curves
        ctx.lineTo(curve1[len1 - 1].x, curve1[len1 - 1].y);

        // building opposite area curve (reverse)
        for (i = len1 - 1; i > 0; --i) {
          lineTo(ctx, curve1[i], curve1[i - 1], true);
        }
      }

      var doFill = (ctx, points, mapper, view, color, loop) => {
        var count = points.length;
        var span = view.spanGaps;
        var curve0 = [];
        var curve1 = [];
        var len0 = 0;
        var len1 = 0;
        var i, ilen, index, p0, p1, d0, d1, loopOffset;

        ctx.beginPath();

        for (i = 0, ilen = count; i < ilen; ++i) {
          index = i % count;
          p0 = points[index]._view;
          p1 = mapper(p0, index, view);
          d0 = isDrawable(p0);
          d1 = isDrawable(p1);

          if (loop && loopOffset === undefined && d0) {
            loopOffset = i + 1;
            ilen = count + loopOffset;
          }

          if (d0 && d1) {
            len0 = curve0.push(p0);
            len1 = curve1.push(p1);
          } else if (len0 && len1) {
            if (!span) {
              drawArea(ctx, curve0, curve1, len0, len1);
              len0 = len1 = 0;
              curve0 = [];
              curve1 = [];
            } else {
              if (d0) {
                curve0.push(p0);
              }
              if (d1) {
                curve1.push(p1);
              }
            }
          }
        }

        drawArea(ctx, curve0, curve1, len0, len1);

        ctx.closePath();
        ctx.fillStyle = color;
        ctx.fill();
      }

      for (i = metasets.length - 1; i >= 0; --i) {
        meta = metasets[i].$filler;

        if (!meta || !meta.visible) {
          continue;
        }

        el = meta.el;
        view = el._view;
        points = el._children || [];
        mapper = meta.mapper;

        // NOTE: HERE IS WHERE THE GRADIENT IS DEFINED. ONE COULD PROBABLY CREATE THE GRADIENT BASED ON INPUT DATA INSIDE THE OPTIONS OBJECT.
        color = ctx.createLinearGradient(chart.width / 2, chart.height, chart.width / 2, 0);
        color.addColorStop(0, 'red');
        color.addColorStop(0.2, 'red');
        color.addColorStop(0.4, 'yellow');
        color.addColorStop(0.6, 'yellow');
        color.addColorStop(0.8, 'green');
        color.addColorStop(1, 'green');

        if (mapper && color && points.length) {
          clipArea(ctx, chart.chartArea);
          doFill(ctx, points, mapper, view, color, el._loop);
          unclipArea(ctx);
        }
      }
    }
  }]
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.9.3/Chart.bundle.js"></script>
<div class="container">
  <canvas id="chart"></canvas>
</div>

3👍

Let me present a generic approach that works with any such chart aslong as its dataset contains zero or positive values only.

The background colors together with the upper values can simply be defined inside the dataset as follows:

bgColors: [
  { color: 'red', upTo: 40 },
  { color: 'yellow', upTo: 70 }, 
  { color: 'green', upTo: 100 }
]

Then you could extend an existing line chart (i.e. ‘lineDiffBgColors’) and overwrite its update function. In there, you would create a linear CanvasGradient and add color stops that correspond to the definitions of bgColors mentioned above. At the end, the linear gradient needs to be assigned to the backgroundColor option of your dataset.

this.chart.data.datasets[0].backgroundColor = gradient;

Please have a look at your enhanced code below.

Chart.defaults.lineDiffBgColors = Chart.defaults.line;
Chart.controllers.lineDiffBgColors = Chart.controllers.line.extend({
  update: function(reset) {
    var yAxis = this.chart.scales['y-axis-0'];
    var bgColors = this.chart.data.datasets[0].bgColors.slice().reverse();
    var max = Math.max.apply(null, bgColors.map(o => o.upTo));
    var min = yAxis.getValueForPixel(yAxis.bottom);
    var yTop = yAxis.getPixelForValue(max);
    var gradient = this.chart.chart.ctx.createLinearGradient(0, yTop, 0, yAxis.bottom);
    let offset = 0;
    bgColors.forEach((bgc, i) => {
      gradient.addColorStop(offset, bgc.color);
      if (i + 1 == bgColors.length) {
        offset = 1;
      } else {         
        offset = (max - bgColors[i + 1].upTo) / (max - min);
      }
      gradient.addColorStop(offset, bgc.color);
    });
    this.chart.data.datasets[0].backgroundColor = gradient;
    return Chart.controllers.line.prototype.update.apply(this, arguments);
  }
});

new Chart('myChart', {
  type: 'lineDiffBgColors',
  data: {
    datasets: [{
      label: 'Dataset',
      data: [
        { x: 1, y: 10 }, 
        { x: 2, y: 50 }, 
        { x: 3, y: 88 }, 
        { x: 4, y: 5 }
      ],
      bgColors: [
        { color: 'red', upTo: 40 },
        { color: 'yellow', upTo: 70 }, 
        { color: 'green', upTo: 100 }
      ]
    }]
  },
  options: {
    scales: {
      xAxes: [{
        type: 'linear'
      }]
    }
  }
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.9.3/Chart.js"></script>
<canvas id="myChart" height="100"></canvas>

In case you prefer kind of smooth gradient, you could change the bgColors.forEach loop inside the update function as follows.

bgColors.forEach((bgc, i) => {    
  gradient.addColorStop(offset == 0 ? 0 : offset + 0.05, bgc.color);
  if (i + 1 == bgColors.length) {
    offset = 1;
  } else {         
    offset = (max - bgColors[i + 1].upTo) / (max - min);
  }
  gradient.addColorStop(offset == 1 ? 1 : offset - 0.05, bgc.color);
});

Leave a comment