Chart.js y轴不同的背景色

5

我在chart js中有一条折线图。我想在y轴上给它不同的背景颜色,比如0-40是红色,40-70是黄色,70-100是绿色。y轴的限制将始终为100。

   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'
            }]
        }
    }
});

如何设置背景?

3个回答

3

目前没有内置选项,但我们可以通过一些代码来实现结果。

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>


你有没有想到任何方法可以将颜色限制在图形下方的区域内?此外,是否有办法使颜色呈现出平滑的渐变效果? - Vishal Jain
1
抱歉,@V.Jain,如果有一种方法可以仅在图形限定区域内限制颜色,我不知道,但请考虑到由于该线是计算出的曲线,这将需要非常复杂的数学计算。关于带状区域的平滑度,我展示了一种绘制背景的方法:我使用了三个简单的ctx.fillRect,但您可以根据自己的喜好玩弄插件。 - Daniele Ricci

3

你可以尝试像这样做一些(丑陋的)事情,代码片段中定义了渐变的注释。

当然,渐变的颜色和组合可以通过像您想要的那样的输入属性确定。此外,可以调整渐变位置或制作径向渐变而不是线性渐变。

最后,这个代码片段确实可以改进,基本上我所做的就是识别图表是如何由库绘制的,以及在它的生命周期的哪个部分完成,然后将其复制到插件中,并用画布线性渐变替换实心背景颜色 ;)

要改进代码片段,我会尝试使用Chart对象内定义的方法(例如lineTo()drawArea())而不是将它们复制到插件内,然后实现options对象中定义的选项来创建线性渐变。

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
让我介绍一种通用方法,只要数据集仅包含零或正值,就可以适用于任何此类图表。背景颜色和上限值可以在数据集中简单地定义如下:
bgColors: [
  { color: 'red', upTo: 40 },
  { color: 'yellow', upTo: 70 }, 
  { color: 'green', upTo: 100 }
]

然后,您可以扩展现有的折线图(即'lineDiffBgColors'),并覆盖其update函数。在其中,您将创建一个线性CanvasGradient并添加与上述bgColors定义相对应的颜色停止点。最后,需要将线性渐变分配给datasetbackgroundColor选项。
this.chart.data.datasets[0].backgroundColor = gradient;

请查看下面增强的代码。

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>

如果你更喜欢平滑的渐变效果,你可以按照以下方式更改update函数内的bgColors.forEach循环。
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);
});

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接