我正在将现有的 Chart JS 2 代码迁移到 3.9.1 并将 Angular 14 升级到 16。这是我的代码:
图表线.component.html
<!--chart container -->
<div id="container">
<div id="scrollArea" class="CMI-ChartWrapper">
<div class="CMI-Chart">
<canvas baseChart #lineCanvas [id]="idChartname"></canvas>
</div>
</div>
<canvas #targetCanvas id="chartAxis" height="400"></canvas>
</div>
图表线.component.ts
import { Component, OnInit, ViewChild, ElementRef, Input, OnDestroy } from "@angular/core";
import Chart from "chart.js/auto";
import ChartDataLabels from 'chartjs-plugin-datalabels';
import { DataServiceService } from 'src/app/services/data-service/data-service.service';
import { data } from '../data';
@Component({
selector: 'app-chart-line',
templateUrl: './chart-line.component.html',
styleUrls: ['./chart-line.component.scss'],
})
export class ChartLineComponent implements OnInit, OnDestroy {
@ViewChild('scroll') scroll: any;
@ViewChild('targetCanvas', { static: true }) targetCanvasRef: ElementRef<HTMLCanvasElement>;
@ViewChild("lineCanvas", { static: true }) lineCanvasRef: ElementRef<HTMLCanvasElement>;
@Input('idname') idChartname: any;
data: any;
public myChart: Chart;
@Input() resizedata: any;
constructor(private dataservice: DataServiceService) {
this.data = data[0].data1;
this.dataservice.saveData(undefined);
}
ngOnInit() {
if (this.resizedata != undefined) {
this.idChartname = this.resizedata;
}
}
ngAfterViewInit(): void {
if (this.resizedata != undefined) {
this.idChartname = this.resizedata;
}
this.dataservice.getFilteredData1.subscribe(message => {
if (message) {
var filteredData = this.filterFunction(message);
this.renderChart(filteredData);
}
else {
this.renderChart(this.data);
}
});
}
filterFunction(filterParams: any) {
var newChartData = [...this.data]
if (filterParams.searchTerm == '') {
var filteredYearData = []
filterParams.selectedYear.forEach(element => {
var newItem = newChartData.filter(item => {
return item['year'].toLowerCase().includes(element);
})
filteredYearData.push(newItem[0]);
});
return filteredYearData;
}
else if (filterParams.selectedYear == '' || filterParams.selectedYear == 'none') {
var filterYear = newChartData.filter(item => {
return item['year'].toLowerCase().includes(filterParams.searchTerm.toLowerCase());
})
var filterProfit = newChartData.filter(item => {
return item['profit'].toLowerCase().includes(filterParams.searchTerm.toLowerCase());
})
if (filterProfit.length == 0) {
filterProfit = filterYear
}
var filterValue = newChartData.filter(item => {
return item['value'].toLowerCase().includes(filterParams.searchTerm.toLowerCase());
})
if (filterValue.length == 0) {
filterValue = filterProfit
}
return filterValue;
}
else {
var filteredYearData = []
filterParams.selectedYear.forEach(element => {
var newItem = newChartData.filter(item => {
return item['year'].toLowerCase().includes(element);
})
filteredYearData.push(newItem[0]);
});
var filterYear = filteredYearData;
var filterProfit = filterYear.filter(item => {
return item['profit'].toLowerCase().includes(filterParams.searchTerm.toLowerCase());
})
var filterValue = filterYear.filter(item => {
return item['value'].toLowerCase().includes(filterParams.searchTerm.toLowerCase());
})
var filterAllData
if (filterProfit.length == 0 && filterValue.length != 0) {
filterAllData = filterValue;
}
else if (filterProfit.length != 0 && filterValue.length == 0) {
filterAllData = filterProfit;
}
else {
filterAllData = filterValue;
}
return filterAllData;
}
}
renderChart(data: any) {
if (this.myChart) {
this.myChart.destroy();
}
Chart.register(ChartDataLabels);
Chart.defaults.plugins.datalabels.anchor = 'start';
Chart.defaults.plugins.datalabels.align = 'start';
Chart.defaults.scale.grid.drawOnChartArea = false;
Chart.defaults.scale.grid.drawOnChartArea = false;
Chart.defaults.plugins.legend.labels.usePointStyle = true;
const lineCanvas: any = document.getElementById(this.idChartname);
const targetCanvas: any = document.getElementById("chartAxis");
if (lineCanvas != null && targetCanvas != null) {
const ctx = targetCanvas.getContext("2d");
let rectangleSet = false;
const targetCtx = targetCanvas.getContext("2d");
this.myChart = new Chart(ctx, {
type: "line",
data: {
labels: data.map(x => x.year),
datasets: [
{
label: "Margin",
fill: false,
tension: 0.2,
backgroundColor: "rgba(75,192,192,0.4)",
borderColor: "#006C5B",
borderCapStyle: "butt",
borderDash: [],
borderDashOffset: 0.0,
borderJoinStyle: "miter",
pointBorderColor: "#006C5B",
pointBackgroundColor: "#006C5B",
pointBorderWidth: 1,
pointHoverRadius: 5,
pointHoverBackgroundColor: "#006C5B",
pointHoverBorderColor: "#003453",
pointHoverBorderWidth: 2,
pointRadius: 4,
pointHitRadius: 10,
spanGaps: false,
data: data.map(x => x.value),
borderWidth: 1
}
]
},
/* chart options for design */
options: {
maintainAspectRatio: false,
aspectRatio: 1,
responsive: true,
scales: {
x: {
ticks: {
// fontColor: 'black',
// fontStyle: 'bold',
},
grid: {
color: 'rgba(171,171,171,1)',
lineWidth: 2
},
title: {
display: true,
text: 'Year',
// fontColor: 'black',
font:{
family:'Helvetica Neue',
size:14,
}
//fontStyle: 'bold',
}
},
y: {
beginAtZero: true,
ticks: {
padding: 0,
// stepSize:10,
// fontColor: 'black',
//fontStyle: 'bold',
callback: function (value) {
return value + "%"
}
},
grid: {
color: 'rgba(171,171,171,1)',
lineWidth: 2
},
title: {
display: true,
text: 'Margin (%)',
// fontColor: 'black',
font:{
family:'Helvetica Neue',
size:14,
}
// fontStyle:'bold'
},
}
},
layout: {
padding: {
left: 0,
right: 0,
top: 0,
bottom: 0
}
},
plugins: { // ChartsJS DataLabels initialized here
legend: {
display: true,
position: 'top',
labels: {
font: {
family:'Helvetica Neue'
}
// fontColor: '#333',
}
},
datalabels: {
formatter: function (value, context) {
return value + "%"
},
anchor: 'start',
align: 'right',
padding: {
left: 0,
right: 25,
top: 40,
bottom: 0
},
// formatter: Math.round,
font: {
// weight: 'bold',
size: 12,
family: 'Helvetica Neue'
},
}
},
animation: {
onComplete: function () {
if (!rectangleSet) {
const scale = window.devicePixelRatio;
const copyWidth = this.scales.y.width - 10;
const copyHeight = this.scales.y.chart.height + this.scales.y.top + 10;
targetCtx.scale(scale, scale);
targetCtx.canvas.width = copyWidth;
targetCtx.canvas.height = copyHeight;
targetCtx.canvas.style.width = copyWidth + 'px';
targetCtx.canvas.style.height = copyHeight + 'px';
targetCtx.drawImage(lineCanvas, 0, 0, copyWidth * scale, copyHeight * scale, 0, 0, copyWidth * scale, copyHeight * scale);
// ctx.clearRect(0, 0, copyWidth, copyHeight);
targetCtx.clearRect(0, 0, copyWidth, copyHeight);
rectangleSet = true;
}
},
onProgress: function () {
if (rectangleSet) {
var copyWidth = this.scales.y.width;
var copyHeight = this.scales.y.height + this.scales.y.top + 10;
this.ctx.clearRect(0, 0, copyWidth, copyHeight);
}
},
},
},
});
} else {
//this.myChart.update();
}
}
scrollleft() {
document.getElementById('scrollArea').scrollLeft += -30;
}
scrollright() {
document.getElementById('scrollArea').scrollLeft += 30;
};
ngOnDestroy() {
console.log("destroyed");
this.myChart.destroy();
//this.dataservice.saveData(undefined);
}
}
数据服务.service.ts
import { Injectable } from '@angular/core';
import { BehaviorSubject, Observable } from 'rxjs';
@Injectable({
providedIn: 'root'
})
export class DataServiceService {
private filteredData = new BehaviorSubject<any>("");
getFilteredData1: Observable<any>;
constructor() {
this.getFilteredData1 = this.filteredData.asObservable();
}
saveData(value) {
this.filteredData.next(value);
}
}
boundry.component.html
<ion-card class="component-boundry content_class">
<!-- Charts -->
<ng-container *ngIf="renderComponent=='chart-animated-line'">
<app-chart-animated-line></app-chart-animated-line>
</ng-container>
<ng-container *ngIf="renderComponent=='chart-bar'">
<app-chart-bar idname="barChartId1"></app-chart-bar>
</ng-container>
<ng-container *ngIf="renderComponent=='chart-line'">
<app-chart-line idname="lineChartId1"></app-chart-line>
</ng-container>
<ng-container *ngIf="renderComponent=='chart-multiple'">
<app-chart-multiple></app-chart-multiple>
</ng-container>
<!--<ng-container *ngIf="renderComponent=='chart-pie'">
<app-chart-pie></app-chart-pie>
</ng-container>-->
<ng-container *ngIf="renderComponent=='chart-bubble'">
<app-chart-bubble></app-chart-bubble>
</ng-container>
<!-- file explorer -->
<ng-container *ngIf="renderComponent=='file-attachment'">
<app-attachment></app-attachment>
</ng-container>
</ion-card>
出现错误
ERROR Error: Canvas is already in use. Chart with ID '11' must be destroyed before the canvas with ID 'chartAxis' can be reused.
因为它没有被 ngOnDestroy 方法破坏。使用 Angular 14 及以下版本和 Chart JS 2 可以正常工作。我不明白为什么 16 会发生这种情况。请帮忙解决。
正如错误所述,您正在硬编码 ID
chartAxis
,因此多个 html 元素将重复相同的 ID,这是错误的,您可以尝试以下更改,我们通过附加 将 ID 设置为动态-Axis
添加到您作为输入提供的动态 ID 的末尾,这可能会解决您的问题!
图表线.component.html
<!--chart container -->
<div id="container">
<div id="scrollArea" class="CMI-ChartWrapper">
<div class="CMI-Chart">
<canvas baseChart #lineCanvas [id]="idChartname"></canvas>
</div>
</div>
<canvas #targetCanvas [id]="idChartname+'-Axis'" height="400"></canvas>
</div>
图表线.component.ts
...
Chart.defaults.plugins.legend.labels.usePointStyle = true;
...
const lineCanvas: any = document.getElementById(this.idChartname);
const targetCanvas: any = document.getElementById(`${this.idChartname}-Axis`); // <-- changed here!
...