我在 ng2-charts(版本 4.1.1,chart.js 版本为 4.4.0)中遇到以下问题:我制作了一个插件来根据某些阈值对折线图的不同部分进行着色。我从this Question获得了关于插件实现的帮助。阈值作为输入变量传递到包含图表的组件。我需要同时实例化多个图表(想象它们在仪表板上,采用某种网格布局),每个图表都有不同的阈值。
问题是,由于我制作的插件是在 Chart.js 中全局注册的,因此对于所有图表来说,它获得的阈值似乎都是首先通过的阈值,即使我使用
this
来引用类的实例变量(并且这些变量确实会更新)。例如,我有两个图表,第一个接收 minThreshold: 10
、maxThreshold: 20
,第二个接收 minThreshold: 20
、maxThreshold: 40
,但插件同时考虑 10 和 20。
稍微解释一下代码,
updateDataPointsColor
负责为折线图上的点着色。我还使用注释插件渲染一条水平虚线来表示阈值,顺便说一句,有趣的是,尽管后一个插件是全局注册的,但它能够动态获取阈值,如下所示我在选项对象中更新它们。我想对我的插件执行相同的操作,或者(如果有意义的话)在本地注册它(但我似乎无法使其工作)。
有人对如何解决这个问题有任何建议或想法吗?
这是我的代码:
import { ChangeDetectorRef, Component, Input, OnChanges, Output, SimpleChanges, ViewChild } from '@angular/core';
import { Chart, ChartDataset, ChartOptions, ChartType } from 'chart.js';
import { BaseChartDirective } from 'ng2-charts';
import { Subject } from 'rxjs';
import annotationPlugin from 'chartjs-plugin-annotation';
@Component({
selector: 'chart',
templateUrl: './chart.component.html',
styleUrls: ['./chart.component.css']
})
export class ChartComponent implements OnChanges {
@Input()
get data(): any[] { return this._data; }
set data(data: any[]) {
this._data = data;
this.updateDataPointsColor();
this.updateChart();
}
private _data!: any[];
@Input()
get yAxes(): any { return this._yAxes; }
set yAxes(yAxes: any) {
this._yAxes = yAxes;
this.lineChartOptions.scales['y'] = this._yAxes;
this.updateDataPointsColor();
this.updateChart();
}
private _yAxes: any;
@Input()
maxThreshold: number = undefined;
@Input()
minThreshold: number = undefined;
@Output() onCloseChartsDashlet: Subject<void> = new Subject<void>();
display: boolean = true;
destroyed: Subject<boolean> = new Subject();
@ViewChild(BaseChartDirective) chart!: BaseChartDirective;
// contains _data array
public lineChartData: ChartDataset[];
public lineChartLabels: any[];
public lineChartOptions: ChartOptions = {
animation: false,
responsive: true,
maintainAspectRatio: false,
scales: {
x: {
display: false,
title: {
display: false,
text: 'Time',
font: {
weight: 'bold',
size: 14
}
},
ticks: {
display: true,
padding: 10
},
grid: {
color: "rgba(0, 0, 0, 0)"
}
}
},
datasets: {
line: {
backgroundColor: 'rgba(255, 0, 0, 0.3)',
borderColor: 'rgba(31, 119, 180, 1)',
pointBackgroundColor: 'rgba(31, 119, 180, 1)',
pointBorderColor: '#fff',
pointHoverBackgroundColor: '#fff',
pointHoverBorderColor: 'rgba(31, 119, 180, 1)',
tension: 0.1
},
},
plugins: {
annotation: {
annotations: [
{
display: this.minThreshold ? true : false,
type: 'line',
id: 'minThreshold',
scaleID: 'y',
value: this.minThreshold,
borderColor: 'red',
borderDash: [5, 5],
borderWidth: 0.5,
drawTime: 'afterDatasetsDraw'
},
{
display: this.maxThreshold ? true : false,
type: 'line',
id: 'maxThreshold',
scaleID: 'y',
value: this.maxThreshold,
borderColor: 'red',
borderDash: [5, 5],
borderWidth: 0.5,
drawTime: 'afterDatasetsDraw'
},
]
}
}
};
public lineChartLegend = false;
public lineChartType: ChartType = 'line';
public chartName = null;
constructor(private cdRef: ChangeDetectorRef) {
Chart.register(annotationPlugin);
}
ngOnChanges(changes: SimpleChanges): void {
if (changes.minThreshold ||
changes.maxThreshold) {
if (changes.minThreshold) {
this.lineChartOptions.plugins.annotation.annotations[0].value = this.minThreshold;
this.lineChartOptions.plugins.annotation.annotations[0].display = this.minThreshold ? true : false;
} else {
this.lineChartOptions.plugins.annotation.annotations[1].value = this.maxThreshold;
this.lineChartOptions.plugins.annotation.annotations[1].display = this.maxThreshold ? true : false;
}
if (this.minThreshold !== undefined ||
this.maxThreshold !== undefined) {
this.registerPlugins();
this.updateDataPointsColor();
this.updateChart();
}
}
}
ngOnDestroy(): void {
//console.log("chart destroyed");
}
/**
* Set data labels
*/
private setLabels() {
// do something
}
/**
* Update chart with new data
*/
updateChart() {
this.lineChartData = [{
data: this._data.map(el => {
return {
x: el.timestamp,
y: el.value
}
}),
fill: false
}];
this.setLabels();
this.chart?.update();
}
/**
* Change some data points color upon a condition
*/
updateDataPointsColor() {
let dataColors = this.lineChartOptions.datasets.line;
let colorsArray: any = [];
for (const dataPoint of this.data) {
let color = 'rgba(31, 119, 180, 1)';
if (dataPoint.value >= this.maxThreshold ||
dataPoint.value <= this.minThreshold) {
color = 'red';
}
colorsArray.push(color);
}
dataColors.pointBackgroundColor = colorsArray;
dataColors.pointHoverBorderColor = colorsArray;
}
private registerPlugins() {
Chart.register(
{
id: "color-by-min-max-thresholds",
afterLayout: chart => {
if (!this.maxThreshold || !this.minThreshold) {
return;
}
let ctx = chart.ctx;
ctx.save();
let yAxis = chart.scales["y"];
let yThresholdMax = this.maxThreshold ? yAxis.getPixelForValue(this.maxThreshold) : undefined;
let yThresholdMin = this.minThreshold ? yAxis.getPixelForValue(this.minThreshold) : undefined;
let offsetMax = yThresholdMax ? (yThresholdMax - yAxis.top) / (yAxis.bottom - yAxis.top) : 0;
let offsetMin = yThresholdMin ? (yThresholdMin - yAxis.top) / (yAxis.bottom - yAxis.top) : 1;
if (offsetMax < 0) {
offsetMax = 0;
} else if (offsetMax > 1) {
offsetMax = 1;
}
if (offsetMin < 0) {
offsetMin = 0;
} else if (offsetMin > 1) {
offsetMin = 1;
}
let gradient = ctx.createLinearGradient(0, yAxis.top, 0, yAxis.bottom);
gradient.addColorStop(0, 'red');
gradient.addColorStop(offsetMax, 'red');
gradient.addColorStop(offsetMax, 'rgba(31, 119, 180, 1)');
gradient.addColorStop(offsetMin, 'rgba(31, 119, 180, 1)');
gradient.addColorStop(offsetMin, 'red');
gradient.addColorStop(1, 'red');
chart.data.datasets[0].borderColor = gradient;
ctx.restore();
}
}
);
}
}
我不明白的另一件事是,我“被迫”使用 OnChanges 钩子来调用
registerPlugins
,因为否则,阈值将是未定义的(我尝试在 ngOnInit 和 ngAfterViewInit 中注册插件,但是没有成功;在钩子中设置了值,但在插件函数中它们是未定义的)。
// Modify the plugin to accept threshold values as parameters
const colorByMinMaxThresholds = {
id: "color-by-min-max-thresholds",
afterLayout: (chart: Chart) => {
const options = chart.options;
const minThreshold = options.plugins.minThreshold;
const maxThreshold = options.plugins.maxThreshold;
if (!maxThreshold || !minThreshold) {
return;
}
// Plugin logic to handle threshold values...
}
};
@Component({
selector: 'chart',
templateUrl: './chart.component.html',
styleUrls: ['./chart.component.css']
})
export class ChartComponent implements OnChanges {
@Input() maxThreshold: number = undefined;
@Input() minThreshold: number = undefined;
constructor(private cdRef: ChangeDetectorRef) {
Chart.register(annotationPlugin);
Chart.register(colorByMinMaxThresholds); // Register the modified plugin
}
ngOnChanges(changes: SimpleChanges): void {
// Update the threshold values in chart options when they change
if (this.chart && (changes.minThreshold || changes.maxThreshold)) {
this.chart.options.plugins.minThreshold = this.minThreshold;
this.chart.options.plugins.maxThreshold = this.maxThreshold;
this.chart.update(); // Update the chart to reflect changes
}
}
// Other component code...
}