I'm migrating existing chart js 2 code to 3.9.1 and upgraded angular 14 to 16. Here is my code:
chart-line.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>
chart-line.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);
}
}
data-service.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>
It's giving error ERROR Error: Canvas is already in use. Chart with ID '11' must be destroyed before the canvas with ID 'chartAxis' can be reused.
As it's not getting destroyed with ngOnDestroy method. With angular 14 and below versions with chart js 2 it's working fine. I'm not getting why this is happening with 16. Please help with this.
As the error says, you are hard coding the ID
chartAxis, so the same ID will be repeateded for multiple html elements, which is wrong, could you try the below change, where we set the ID to be dynamic by appending-Axisto the end of the dynamic ID you provide as input, this might solve your problem!chart-line.component.html
chart-line.component.ts