Search code examples
javascripthtml5-canvaschart.jsvue-chartjs

ChartJs, How can I get different color fills between my two datasets in a line graph?


graph[1]

Above is a graph I made using ChartJS. I have two datasets. I also have the space between the two datasets filled just like I want it. But I need to get two different colors for the fill. When the Hours dataset is bigger than the Goal dataset, I want the fill to be green, and when the Hours dataset is smaller than Goal, I want it to be red. I'm really hoping there is a way to do this using ChartJS, so that I don't have to recreate this in Canvas.

If it matters, this is in Vue.Js.

Here is the code for the two datasets:

    const dataSets = [{

        label: this.dataSetLabel,
        data: this.dataArray,
        backgroundColor: new Array(this.dataArray.length).fill('rgba(255, 0, 0, 0.8)'), // red
        borderColor: new Array(this.dataArray.length).fill('rgba(255, 0, 0, 0.8)'),
        borderWidth: 1,
        fill: '+1'
      }]
      if (this.fi !== 3) {

        dataSets.push({
          label: 'Goal',
          data: new Array(this.dataArray.length).fill(this.user.braceHours),
          type: 'line',
          backgroundColor: new Array(this.dataArray.length).fill('rgba(84, 232, 198, 0.8)'), // green
          borderColor: new Array(this.dataArray.length).fill('rgba(84, 232, 198, 0.8)'),
          borderWidth: 1,
          fill: '-1'
        })
      }

Any help with this would be much appreciated.


Solution

  • You can extend an existing line chart as shown by the runnable code snippted below.

    This solution is based on the answer https://stackoverflow.com/a/36941860/2358409 that had to be slightly adapted to work with the latest stable version of Chart.js (2.9.3).

    const goal = 2;
    Chart.defaults.areaConditionalColors = Chart.defaults.line;
    Chart.controllers.areaConditionalColors = Chart.controllers.line.extend({
      update: function(reset) {
        var yAxis = this.chart.scales['y-axis-0'];  
        var max = Math.max.apply(null, this.chart.data.datasets[0].data);
        var yTop = yAxis.getPixelForValue(max);
        var yGoal = yAxis.getPixelForValue(goal);
        var min = Math.min.apply(null, this.chart.data.datasets[0].data);
        var yBottom = yAxis.getPixelForValue(min);
    
        // build a gradient that changes the color at the goal
        var ctx = this.chart.chart.ctx;
        var gradient = ctx.createLinearGradient(0, yTop, 0, yBottom);
        var ratio = Math.min((yGoal - yTop) / (yBottom - yTop), 1);
        gradient.addColorStop(0, 'green');
        gradient.addColorStop(ratio, 'green');
        gradient.addColorStop(ratio, 'red');
        gradient.addColorStop(1, 'red');
        this.chart.data.datasets[0].backgroundColor = gradient;
    
        return Chart.controllers.line.prototype.update.apply(this, arguments);
      }
    });
    
    new Chart('myChart', {
      type: 'areaConditionalColors',
      data: {
        labels: ['FRI', 'SAT', 'SUN', 'MON', 'TUE', 'WED', 'THU'],
        datasets: [{
          label: 'Hours',
          backgroundColor: 'green',
          data: [0, 3, 0, 9, 4, 0, 0],
          fill: '+1'
        },
        {
          label: 'Goal',
          backgroundColor: 'rgba(84, 232, 198, 0.8)',
          data: [goal, goal, goal, goal, goal, goal, goal],
          fill: '-1'
        }]
      },
      options: {
        legend: {
          onClick: e => e.stopPropagation()
        },
        tooltips: {
          mode: 'x'
        }
      }  
    });
    <script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.9.3/Chart.min.js"></script>
    <canvas id="myChart" height="80"></canvas>