diff --git a/npm/ng-packs/apps/dev-app/src/app/app.module.ts b/npm/ng-packs/apps/dev-app/src/app/app.module.ts
index 9c89658357..21eb869af0 100644
--- a/npm/ng-packs/apps/dev-app/src/app/app.module.ts
+++ b/npm/ng-packs/apps/dev-app/src/app/app.module.ts
@@ -1,4 +1,5 @@
import { AccountConfigModule } from '@abp/ng.account/config';
+import { ChartModule } from '@abp/ng.components/chart.js';
import { CoreModule } from '@abp/ng.core';
import { registerLocale } from '@abp/ng.core/locale';
import { IdentityConfigModule } from '@abp/ng.identity/config';
@@ -31,6 +32,7 @@ import { APP_ROUTE_PROVIDER } from './route.provider';
TenantManagementConfigModule.forRoot(),
SettingManagementConfigModule.forRoot(),
ThemeBasicModule.forRoot(),
+ ChartModule.forRoot()
],
providers: [APP_ROUTE_PROVIDER],
declarations: [AppComponent],
diff --git a/npm/ng-packs/packages/components/chart.js/ng-package.json b/npm/ng-packs/packages/components/chart.js/ng-package.json
new file mode 100644
index 0000000000..cc98fd4e36
--- /dev/null
+++ b/npm/ng-packs/packages/components/chart.js/ng-package.json
@@ -0,0 +1,7 @@
+{
+ "$schema": "../../../node_modules/ng-packagr/ng-package.schema.json",
+ "dest": "../../../dist/packages/components/chart.js",
+ "lib": {
+ "entryFile": "src/public-api.ts"
+ }
+}
diff --git a/npm/ng-packs/packages/components/chart.js/src/chart.component.html b/npm/ng-packs/packages/components/chart.js/src/chart.component.html
new file mode 100644
index 0000000000..211614c90d
--- /dev/null
+++ b/npm/ng-packs/packages/components/chart.js/src/chart.component.html
@@ -0,0 +1,11 @@
+
+
+
diff --git a/npm/ng-packs/packages/components/chart.js/src/chart.component.ts b/npm/ng-packs/packages/components/chart.js/src/chart.component.ts
new file mode 100644
index 0000000000..499b0b5e33
--- /dev/null
+++ b/npm/ng-packs/packages/components/chart.js/src/chart.component.ts
@@ -0,0 +1,141 @@
+import {
+ AfterViewInit,
+ ChangeDetectorRef,
+ Component,
+ ElementRef,
+ EventEmitter,
+ Input,
+ OnDestroy,
+ Output
+} from '@angular/core';
+import { BehaviorSubject } from 'rxjs';
+import { chartJsLoaded$ } from './widget-utils';
+declare const Chart: any;
+
+@Component({
+ selector: 'abp-chart',
+ templateUrl: './chart.component.html',
+})
+export class ChartComponent implements AfterViewInit, OnDestroy {
+ @Input() type: string;
+
+ @Input() options: any = {};
+
+ @Input() plugins: any[] = [];
+
+ @Input() width: string;
+
+ @Input() height: string;
+
+ @Input() responsive = true;
+
+ // eslint-disable-next-line @angular-eslint/no-output-on-prefix
+ @Output() readonly onDataSelect: EventEmitter = new EventEmitter();
+
+ @Output() readonly initialized = new BehaviorSubject(this);
+
+ private _initialized: boolean;
+
+ _data: any;
+
+ chart: any;
+
+ constructor(public el: ElementRef, private cdRef: ChangeDetectorRef) {}
+
+ @Input() get data(): any {
+ return this._data;
+ }
+
+ set data(val: any) {
+ this._data = val;
+ this.reinit();
+ }
+
+ get canvas() {
+ return this.el.nativeElement.children[0].children[0];
+ }
+
+ get base64Image() {
+ return this.chart.toBase64Image();
+ }
+
+ ngAfterViewInit() {
+ chartJsLoaded$.subscribe(() => {
+ this.testChartJs();
+
+ this.initChart();
+ this._initialized = true;
+ });
+ }
+
+ testChartJs() {
+ try {
+ Chart;
+ } catch (error) {
+ throw new Error(`Chart is not found. Import the Chart from app.module like shown below:
+ import('chart.js');
+ `);
+ }
+ }
+
+ onCanvasClick = event => {
+ if (this.chart) {
+ const element = this.chart.getElementAtEvent(event);
+ const dataset = this.chart.getDatasetAtEvent(event);
+ if (element && element.length && dataset) {
+ this.onDataSelect.emit({
+ originalEvent: event,
+ element: element[0],
+ dataset,
+ });
+ }
+ }
+ };
+
+ initChart = () => {
+ const opts = this.options || {};
+ opts.responsive = this.responsive;
+
+ // allows chart to resize in responsive mode
+ if (opts.responsive && (this.height || this.width)) {
+ opts.maintainAspectRatio = false;
+ }
+
+ this.chart = new Chart(this.canvas, {
+ type: this.type,
+ data: this.data,
+ options: this.options,
+ plugins: this.plugins,
+ });
+
+ this.cdRef.detectChanges();
+ };
+
+ generateLegend = () => {
+ if (this.chart) {
+ return this.chart.generateLegend();
+ }
+ };
+
+ refresh = () => {
+ if (this.chart) {
+ this.chart.update();
+ this.cdRef.detectChanges();
+ }
+ };
+
+ reinit = () => {
+ if (this.chart) {
+ this.chart.destroy();
+ this.initChart();
+ }
+ };
+
+ ngOnDestroy() {
+ if (this.chart) {
+ this.chart.destroy();
+ this._initialized = false;
+ this.chart = null;
+ }
+ }
+}
diff --git a/npm/ng-packs/packages/components/chart.js/src/chart.module.ts b/npm/ng-packs/packages/components/chart.js/src/chart.module.ts
new file mode 100644
index 0000000000..47e422174c
--- /dev/null
+++ b/npm/ng-packs/packages/components/chart.js/src/chart.module.ts
@@ -0,0 +1,29 @@
+import { APP_INITIALIZER, Injector, ModuleWithProviders, NgModule } from '@angular/core';
+import { ChartComponent } from './chart.component';
+
+declare const Chart: any
+
+@NgModule({
+ imports: [],
+ exports: [],
+ declarations: [ChartComponent],
+ providers: [],
+})
+export class ChartModule {
+ static forRoot(): ModuleWithProviders {
+ return {
+ ngModule: ChartModule,
+ providers: [{
+ provide: APP_INITIALIZER,
+ multi: true,
+ useFactory: (injector: Injector) => () => {
+ import('chart.js/auto').then((module) => {
+ console.dir(module)
+ })
+ return Promise.resolve()
+ },
+ deps: [Injector]
+ }]
+ }
+ }
+}
diff --git a/npm/ng-packs/packages/components/chart.js/src/public-api.ts b/npm/ng-packs/packages/components/chart.js/src/public-api.ts
new file mode 100644
index 0000000000..399f89f358
--- /dev/null
+++ b/npm/ng-packs/packages/components/chart.js/src/public-api.ts
@@ -0,0 +1,4 @@
+export * from './chart.component';
+export * from './chart.module';
+export * from './widget-utils';
+
diff --git a/npm/ng-packs/packages/components/chart.js/src/widget-utils.ts b/npm/ng-packs/packages/components/chart.js/src/widget-utils.ts
new file mode 100644
index 0000000000..dfe81c973c
--- /dev/null
+++ b/npm/ng-packs/packages/components/chart.js/src/widget-utils.ts
@@ -0,0 +1,16 @@
+import { ReplaySubject } from 'rxjs';
+
+export function getRandomBackgroundColor(count) {
+ const colors = [];
+
+ for (let i = 0; i < count; i++) {
+ const r = ((i + 5) * (i + 5) * 474) % 255;
+ const g = ((i + 5) * (i + 5) * 1600) % 255;
+ const b = ((i + 5) * (i + 5) * 84065) % 255;
+ colors.push('rgba(' + r + ', ' + g + ', ' + b + ', 0.7)');
+ }
+
+ return colors;
+}
+
+export const chartJsLoaded$ = new ReplaySubject(1);
diff --git a/npm/ng-packs/tsconfig.base.json b/npm/ng-packs/tsconfig.base.json
index 415fcd2ce5..674468c130 100644
--- a/npm/ng-packs/tsconfig.base.json
+++ b/npm/ng-packs/tsconfig.base.json
@@ -17,6 +17,7 @@
"@abp/ng.account.core": ["packages/account-core/src/public-api.ts"],
"@abp/ng.account/config": ["packages/account/config/src/public-api.ts"],
"@abp/ng.components": ["packages/components/src/public-api.ts"],
+ "@abp/ng.components/chart.js": ["packages/components/chart.js/src/public-api.ts"],
"@abp/ng.components/page": ["packages/components/page/src/public-api.ts"],
"@abp/ng.components/tree": ["packages/components/tree/src/public-api.ts"],
"@abp/ng.core": ["packages/core/src/public-api.ts"],
diff --git a/npm/ng-packs/yarn.lock b/npm/ng-packs/yarn.lock
index dbc51cc020..0af1a56c06 100644
--- a/npm/ng-packs/yarn.lock
+++ b/npm/ng-packs/yarn.lock
@@ -4945,6 +4945,11 @@ chart.js@^2.9.3:
chartjs-color "^2.1.0"
moment "^2.10.2"
+chart.js@^3.5.1:
+ version "3.5.1"
+ resolved "https://registry.yarnpkg.com/chart.js/-/chart.js-3.5.1.tgz#73e24d23a4134a70ccdb5e79a917f156b6f3644a"
+ integrity sha512-m5kzt72I1WQ9LILwQC4syla/LD/N413RYv2Dx2nnTkRS9iv/ey1xLTt0DnPc/eWV4zI+BgEgDYBIzbQhZHc/PQ==
+
chartjs-color-string@^0.6.0:
version "0.6.0"
resolved "https://registry.yarnpkg.com/chartjs-color-string/-/chartjs-color-string-0.6.0.tgz#1df096621c0e70720a64f4135ea171d051402f71"