Skip to content

Commit 83f5f8e

Browse files
committed
fix(chartjs): canvas already in use, refactor
1 parent af7ce16 commit 83f5f8e

File tree

1 file changed

+91
-41
lines changed

1 file changed

+91
-41
lines changed

projects/coreui-angular-chartjs/src/lib/chartjs.component.ts

+91-41
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,19 @@ import {
2222

2323
import merge from 'lodash-es/merge';
2424

25-
import { Chart, ChartConfiguration, ChartType, DefaultDataPoint, registerables } from 'chart.js';
25+
import {
26+
Chart as ChartJS,
27+
ChartConfiguration,
28+
ChartData,
29+
ChartOptions,
30+
ChartType,
31+
InteractionItem,
32+
Plugin,
33+
registerables
34+
} from 'chart.js';
2635
import { customTooltips as cuiCustomTooltips } from '@coreui/chartjs';
2736

28-
Chart.register(...registerables);
37+
ChartJS.register(...registerables);
2938

3039
let nextId = 0;
3140

@@ -39,26 +48,70 @@ let nextId = 0;
3948
// eslint-disable-next-line @angular-eslint/no-host-metadata-property
4049
// host: { ngSkipHydration: 'true' }
4150
})
42-
export class ChartjsComponent<TType extends ChartType = ChartType, TData = DefaultDataPoint<TType>, TLabel = unknown> implements AfterViewInit, OnDestroy, OnChanges {
43-
44-
@Input() customTooltips = true;
45-
@Input() data?: ChartConfiguration<TType, TData, TLabel>['data'];
46-
51+
export class ChartjsComponent implements AfterViewInit, OnDestroy, OnChanges {
52+
53+
/**
54+
* Enables custom html based tooltips instead of standard tooltips.
55+
* @type boolean
56+
* @default true
57+
*/
58+
@Input({ transform: booleanAttribute }) customTooltips: boolean = true;
59+
60+
/**
61+
* The data object that is passed into the Chart.js chart (more info).
62+
*/
63+
@Input() data?: ChartData;
64+
65+
/**
66+
* Height attribute applied to the rendered canvas.
67+
* @type number | undefined
68+
* @default 150
69+
*/
4770
@HostBinding('style.height.px')
48-
@Input({ transform: (value: string | number) => numberAttribute(value, undefined) }) height?: string | number;
49-
50-
@Input() id = `c-chartjs-${nextId++}`;
51-
@Input() options?: ChartConfiguration<TType, TData, TLabel>['options'];
52-
@Input() plugins: ChartConfiguration<TType, TData, TLabel>['plugins'] = [];
53-
54-
@Input({ transform: booleanAttribute }) redraw: string | boolean = false;
55-
56-
@Input() type: ChartConfiguration<TType, TData, TLabel>['type'] = 'bar' as TType;
57-
71+
@Input({ transform: (value: string | number) => numberAttribute(value, undefined) }) height?: number;
72+
73+
/**
74+
* ID attribute applied to the rendered canvas.
75+
* @type string
76+
*/
77+
@Input() id: string = `c-chartjs-${nextId++}`;
78+
79+
/**
80+
* The options object that is passed into the Chart.js chart.
81+
*/
82+
@Input() options?: ChartOptions = {};
83+
84+
/**
85+
* The plugins array that is passed into the Chart.js chart
86+
*/
87+
@Input() plugins: Plugin[] = [];
88+
89+
/**
90+
* If true, will tear down and redraw chart on all updates.
91+
* @type boolean
92+
* @default false
93+
*/
94+
@Input({ transform: booleanAttribute }) redraw: boolean = false;
95+
96+
/**
97+
* Chart.js chart type.
98+
* @type {'line' | 'bar' | 'radar' | 'doughnut' | 'polarArea' | 'bubble' | 'pie' | 'scatter'}
99+
*/
100+
@Input() type: ChartType = 'bar';
101+
102+
/**
103+
* Width attribute applied to the rendered canvas.
104+
* @type number | undefined
105+
* @default 300
106+
*/
58107
@HostBinding('style.width.px')
59-
@Input({ transform: (value: string | number) => numberAttribute(value, undefined) }) width?: string | number;
108+
@Input({ transform: (value: string | number) => numberAttribute(value, undefined) }) width?: number;
60109

61-
@Input() wrapper = true;
110+
/**
111+
* Put the chart into the wrapper div element.
112+
* @default true
113+
*/
114+
@Input({ transform: booleanAttribute }) wrapper = true;
62115

63116
@Output() readonly getDatasetAtEvent = new EventEmitter<any>();
64117
@Output() readonly getElementAtEvent = new EventEmitter<any>();
@@ -68,7 +121,7 @@ export class ChartjsComponent<TType extends ChartType = ChartType, TData = Defau
68121

69122
@ViewChild('canvasElement') canvasElement!: ElementRef;
70123

71-
chart!: Chart<TType, TData, TLabel>;
124+
chart!: ChartJS;
72125
ctx!: CanvasRenderingContext2D;
73126

74127
@HostBinding('class')
@@ -79,10 +132,9 @@ export class ChartjsComponent<TType extends ChartType = ChartType, TData = Defau
79132
}
80133

81134
constructor(
82-
private elementRef: ElementRef,
83-
private ngZone: NgZone,
84-
private renderer: Renderer2,
85-
private changeDetectorRef: ChangeDetectorRef
135+
private readonly ngZone: NgZone,
136+
private readonly renderer: Renderer2,
137+
private readonly changeDetectorRef: ChangeDetectorRef
86138
) {
87139
// todo: verify afterRender / afterNextRender for chartjs (spec fails with 17.0.10)
88140
afterRender(() => {
@@ -110,13 +162,13 @@ export class ChartjsComponent<TType extends ChartType = ChartType, TData = Defau
110162
return;
111163
}
112164

113-
const datasetAtEvent = this.chart.getElementsAtEventForMode($event, 'dataset', { intersect: true }, false);
165+
const datasetAtEvent: InteractionItem[] = this.chart.getElementsAtEventForMode($event, 'dataset', { intersect: true }, false);
114166
this.getDatasetAtEvent.emit(datasetAtEvent);
115167

116-
const elementAtEvent = this.chart.getElementsAtEventForMode($event, 'nearest', { intersect: true }, false);
168+
const elementAtEvent: InteractionItem[] = this.chart.getElementsAtEventForMode($event, 'nearest', { intersect: true }, false);
117169
this.getElementAtEvent.emit(elementAtEvent);
118170

119-
const elementsAtEvent = this.chart.getElementsAtEventForMode($event, 'index', { intersect: true }, false);
171+
const elementsAtEvent: InteractionItem[] = this.chart.getElementsAtEventForMode($event, 'index', { intersect: true }, false);
120172
this.getElementsAtEvent.emit(elementsAtEvent);
121173
}
122174

@@ -126,15 +178,15 @@ export class ChartjsComponent<TType extends ChartType = ChartType, TData = Defau
126178
}
127179

128180
public chartRender() {
129-
if (!this.canvasElement?.nativeElement || !this.ctx) {
181+
if (!this.canvasElement?.nativeElement || !this.ctx || this.chart) {
130182
return;
131183
}
132184

133185
this.ngZone.runOutsideAngular(() => {
134186
const config = this.chartConfig();
135187
if (config) {
136-
setTimeout(() => {
137-
this.chart = new Chart(this.ctx, config);
188+
this.chart = new ChartJS(this.ctx, config);
189+
this.ngZone.run(() => {
138190
this.renderer.setStyle(this.canvasElement.nativeElement, 'display', 'block');
139191
this.changeDetectorRef.markForCheck();
140192
this.chartRef.emit(this.chart);
@@ -150,13 +202,11 @@ export class ChartjsComponent<TType extends ChartType = ChartType, TData = Defau
150202

151203
if (this.redraw) {
152204
this.chartDestroy();
153-
setTimeout(() => {
154-
this.chartRender();
155-
});
205+
this.chartRender();
156206
return;
157207
}
158208

159-
const config = this.chartConfig();
209+
const config: ChartConfiguration = this.chartConfig();
160210

161211
if (this.options) {
162212
Object.assign(this.chart.options ?? {}, config.options ?? {});
@@ -180,7 +230,9 @@ export class ChartjsComponent<TType extends ChartType = ChartType, TData = Defau
180230
setTimeout(() => {
181231
this.ngZone.runOutsideAngular(() => {
182232
this.chart?.update();
183-
this.changeDetectorRef.markForCheck();
233+
this.ngZone.run(() => {
234+
this.changeDetectorRef.markForCheck();
235+
});
184236
});
185237
});
186238
}
@@ -189,18 +241,18 @@ export class ChartjsComponent<TType extends ChartType = ChartType, TData = Defau
189241
return this.chart?.toBase64Image();
190242
}
191243

192-
private chartDataConfig(): ChartConfiguration<TType, TData, TLabel>['data'] {
244+
private chartDataConfig(): ChartData {
193245
return {
194246
labels: this.data?.labels ?? [],
195247
datasets: this.data?.datasets ?? []
196248
};
197249
}
198250

199-
private chartOptions(): ChartConfiguration<TType, TData, TLabel>['options'] {
200-
return this.options;
251+
private chartOptions(): ChartOptions {
252+
return this.options ?? {};
201253
}
202254

203-
private chartConfig(): ChartConfiguration<TType, TData, TLabel> {
255+
private chartConfig(): ChartConfiguration {
204256
this.chartCustomTooltips();
205257
return {
206258
data: this.chartDataConfig(),
@@ -213,9 +265,7 @@ export class ChartjsComponent<TType extends ChartType = ChartType, TData = Defau
213265
private chartCustomTooltips() {
214266
if (this.customTooltips) {
215267
const options = this.options;
216-
// @ts-ignore
217268
const plugins = this.options?.plugins;
218-
// @ts-ignore
219269
const tooltip = this.options?.plugins?.tooltip;
220270
this.options = merge({
221271
...options,

0 commit comments

Comments
 (0)