Angular-Chart-JS - 根据点的范围具有不同填充颜色的折线图范围、根据、颜色、不同

2023-09-07 11:58:41 作者:公主殿下是奇葩!

我在我的网站上使用 Angular-Chart-js 来显示某些类型的图表,其中之一是折线图.

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

我希望折线图是彩色的,但根据 y 轴的值使用不同的颜色.就像这张照片:

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

我尝试在图表的数据"数组中使用不同的数据数组,第一个包含所有值,第二个包含所有值,除了涂成绿色的值(右侧),第三个是相同的数组直到紫色范围等,然后每个数据集都有自己的颜色,但最终我根据最后一个数据集颜色得到一个单一颜色的图表.

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?

谢谢.

推荐答案

很遗憾,您无法使用当前的 chart.js 配置选项实现此目的.原因是因为折线图 backgroundColor 选项(控制折线图填充颜色的选项)只接受单个值.

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.

挖掘了当前chart.js 2.5源码后发现可以扩展line元素的draw()方法,强制chart.js使用canvas线性渐变进行填充(而不仅仅是一种颜色).通过一点点数学,我们可以将每个点的 x 位置转换为线性渐变颜色停止位置并构建渐变.

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.

通过此增强功能,您现在可以将一组颜色传递给折线图的 backgroundColor 选项,以实现不同颜色的填充区域.以下是结果图表的示例.

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.

以下是实际操作方法(底部有一个工作示例)

首先,我们必须扩展 Chart.elements.Line 并覆盖它的 draw() 方法,以便我们可以根据每个点的位置构建线性渐变,用它作为线填充,然后画线.

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

完成此操作后,我们现在可以将一组颜色传递给折线图的 backgroundColor 属性(而不仅仅是单个值)来控制线条填充.

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.

这是一个 codepen 示例,它演示了所有已讨论的内容.

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

注意事项:

这种方法可能会在未来的 chart.js 版本中失效,因为我们正在搞乱内部结构.我不熟悉 angular-chart.js,所以我无法提供有关如何将上述 chart.js 更改集成到 angular 指令的见解. This approach could break in future chart.js releases since we are messing with the internals. I'm not familiar with angular-chart.js, so I cannot provide insight on how to integrate the above chart.js changes into the angular directive.