我在我的网站上使用Angular-Chart-js来显示某些类型的图表,其中一个是折线图。
我希望折线图能够填充颜色,但根据y轴的值使用不同的颜色。就像在这张照片中:
我试图在图形的“数据”数组中有不同的数据数组,第一个具有所有值,第二个具有除绿色之外的所有值(右侧),第三个是相同的数组只有紫色范围等,然后每个数据集都有自己的颜色,但最终我根据最后的数据集颜色得到一个单一颜色的图形。
我错过了什么?有没有办法实现这个目标?
感谢。
答案 0 :(得分:2)
不幸的是,您无法使用当前的chart.js配置选项实现此目的。原因是折线图backgroundColor
选项(控制折线图填充颜色的选项)仅接受单个值。
在深入了解当前的chart.js 2.5源码后,我发现可以扩展线元素的draw()
方法并强制chart.js使用画布线性渐变来填充(而不仅仅是单色)。通过一些数学运算,我们可以将每个点的x位置转换为线性渐变颜色停止位置并构建渐变。
通过此增强功能,您现在可以将一系列颜色传递到折线图backgroundColor
选项,以实现不同颜色的填充区域。以下是结果图表的示例。
以下是如何实际操作(底部有一个工作示例)
首先,我们必须扩展Chart.elements.Line
并覆盖它的draw()
方法,以便我们可以根据每个点的位置构建线性渐变,将其用作线条填充,然后绘制线条
// 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);
}
});
然后,我们还必须扩展折线图以确保图表使用的线元素是我们在上面扩展的线元素(因为此属性已在加载时设置)
// 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
属性(而不是单个值)来控制线条填充。
这是一个codepen example,演示了所有讨论过的内容。
警告:
答案 1 :(得分:0)
如果你想在angular2和ng2-charts中拥有这个功能,可能会有更少的"黑客攻击"这样做的方法,但这就是我能够应用Jordan代码使其工作的方式:
无论如何这对我有用。它没有经过全面测试,绝对是一个" hack"但是我不想学习Chart.js 2.7来弄清楚为什么Jordan的代码无法使用它。这肯定是更多"适当"这样做的方式。我想它应该被整合为一个"插件"。
答案 2 :(得分:0)
我决定采用chartJS 2.5方法,但使用上述扩展而不是修改chartjs代码本身。.
我必须进行一些性能优化,因为我的图表具有4000多个值。 获取具有正确值(稀疏替代颜色,可能是4000个值中的200个)构建的颜色数组,然后让扩展读取该值以构建线性渐变非常耗时。埋入我正在用于图表显示系统的树莓派PI。
我最终决定减少处理时间,我需要消除对点列表的任何额外处理。.我的收集,我创建了颜色数组,并用图表构建了线性图形。
因此,现在我遍历数据时创建了linearGradient边缘(全部通过)。渐变是一种结构数组,与数据数组的起始位置有偏移,并且要应用该颜色边缘,基本上可以完成原始扩展的工作..因此,减少800点至40个边缘。或800点到1个边缘(开始)...
所以,这是我更新的扩展功能。我的应用程序具有所有三种颜色类型的图表。单个固定的颜色数组和边缘数组..上面所有其他例程均未更改
// 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);
// if not a single color
if( typeof backgroundColors != 'string' ){
// but is array of colors
if( typeof backgroundColors[0] === 'string' ) {
// iterate over each point to build the gradient
points.forEach(function(point, i) { // start original code
// `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 = self.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]);
}
}
}); // end original code
} // end of if for color array
// must be a gradient fence position list
else {
// loop thru the fence positions
backgroundColors.forEach(function(fencePosition){
var colorStopPosition = self.roundNumber(fencePosition.offset / points.length, 2);
linearGradient.addColorStop(colorStopPosition,fencePosition.color)
});
} // end of block for handling color space edges
// 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;
} // end of if for just one color
// now draw the lines (using the original draw method)
origLineElement.prototype.draw.apply(this);
}