coreui-angular-ex
Version:
CoreUI Components Library for Angular
227 lines (184 loc) • 6.21 kB
text/typescript
import {
AfterViewInit,
booleanAttribute,
ChangeDetectionStrategy,
ChangeDetectorRef,
Component,
ElementRef,
EventEmitter,
HostBinding,
Input,
NgZone,
numberAttribute,
OnChanges,
OnDestroy,
Output,
Renderer2,
SimpleChanges,
ViewChild
} from '@angular/core';
import merge from 'lodash-es/merge';
import { Chart, ChartConfiguration, ChartType, DefaultDataPoint, registerables } from 'chart.js';
import { customTooltips as cuiCustomTooltips } from '@coreui/chartjs';
Chart.register(...registerables);
let nextId = 0;
export class ChartjsComponent<TType extends ChartType = ChartType, TData = DefaultDataPoint<TType>, TLabel = unknown> implements AfterViewInit, OnDestroy, OnChanges {
customTooltips = true;
data?: ChartConfiguration<TType, TData, TLabel>['data'];
height?: string | number;
id = `c-chartjs-${nextId++}`;
options?: ChartConfiguration<TType, TData, TLabel>['options'];
plugins: ChartConfiguration<TType, TData, TLabel>['plugins'] = [];
redraw: string | boolean = false;
type: ChartConfiguration<TType, TData, TLabel>['type'] = 'bar' as TType;
width?: string | number;
wrapper = true;
readonly getDatasetAtEvent = new EventEmitter<any>();
readonly getElementAtEvent = new EventEmitter<any>();
readonly getElementsAtEvent = new EventEmitter<any>();
readonly chartRef = new EventEmitter<any>();
canvasElement!: ElementRef;
chart!: Chart<TType, TData, TLabel>;
get hostClasses() {
return {
'chart-wrapper': this.wrapper
};
}
constructor(
private elementRef: ElementRef,
private ngZone: NgZone,
private renderer: Renderer2,
private changeDetectorRef: ChangeDetectorRef
) {}
ngAfterViewInit(): void {
this.chartRender();
}
ngOnChanges(changes: SimpleChanges): void {
if (changes['data'] && !changes['data'].firstChange) {
this.chartUpdate();
}
}
ngOnDestroy(): void {
this.chartDestroy();
}
public handleClick($event: MouseEvent) {
if (!this.chart) {
return;
}
const datasetAtEvent = this.chart.getElementsAtEventForMode($event, 'dataset', { intersect: true }, false);
this.getDatasetAtEvent.emit(datasetAtEvent);
const elementAtEvent = this.chart.getElementsAtEventForMode($event, 'nearest', { intersect: true }, false);
this.getElementAtEvent.emit(elementAtEvent);
const elementsAtEvent = this.chart.getElementsAtEventForMode($event, 'index', { intersect: true }, false);
this.getElementsAtEvent.emit(elementsAtEvent);
}
public chartDestroy() {
this.chart?.destroy();
this.chartRef.emit(undefined);
}
public chartRender() {
if (!this.canvasElement) {
return;
}
const ctx: CanvasRenderingContext2D = this.canvasElement.nativeElement.getContext('2d');
this.ngZone.runOutsideAngular(() => {
const config = this.chartConfig();
if (config) {
setTimeout(() => {
this.chart = new Chart(ctx, config);
this.renderer.setStyle(this.canvasElement.nativeElement, 'display', 'block');
this.changeDetectorRef.markForCheck();
this.chartRef.emit(this.chart);
});
}
});
}
chartUpdate() {
if (!this.chart) {
return;
}
if (this.redraw) {
this.chartDestroy();
setTimeout(() => {
this.chartRender();
});
return;
}
const config = this.chartConfig();
if (this.options) {
Object.assign(this.chart.options ?? {}, config.options ?? {});
}
if (!this.chart.config.data) {
this.chart.config.data = { ...config.data };
this.chartUpdateOutsideAngular();
}
if (this.chart) {
Object.assign(this.chart.config.options ?? {}, config.options ?? {});
Object.assign(this.chart.config.plugins ?? [], config.plugins ?? []);
Object.assign(this.chart.config.data, config.data);
}
this.chartUpdateOutsideAngular();
}
private chartUpdateOutsideAngular() {
setTimeout(() => {
this.ngZone.runOutsideAngular(() => {
this.chart?.update();
this.changeDetectorRef.markForCheck();
});
});
}
public chartToBase64Image(): string | undefined {
return this.chart?.toBase64Image();
}
private chartDataConfig(): ChartConfiguration<TType, TData, TLabel>['data'] {
return {
labels: this.data?.labels ?? [],
datasets: this.data?.datasets ?? []
};
}
private chartOptions(): ChartConfiguration<TType, TData, TLabel>['options'] {
return this.options;
}
private chartConfig(): ChartConfiguration<TType, TData, TLabel> {
this.chartCustomTooltips();
return {
data: this.chartDataConfig(),
options: this.chartOptions(),
plugins: this.plugins,
type: this.type
};
}
private chartCustomTooltips() {
if (this.customTooltips) {
const options = this.options;
// @ts-ignore
const plugins = this.options?.plugins;
// @ts-ignore
const tooltip = this.options?.plugins?.tooltip;
this.options = merge({
...options,
plugins: {
...plugins,
tooltip: {
...tooltip,
enabled: false,
mode: 'index',
position: 'nearest',
external: cuiCustomTooltips
}
}
});
}
};
}