diff --git a/package.json b/package.json
index 8984a27..cfbc888 100644
--- a/package.json
+++ b/package.json
@@ -58,6 +58,7 @@
"@types/uuid": "^8.3.1",
"ace-builds": "^1.11.2",
"angular-gridster2": "^12.1.0",
+ "apexcharts": "^6.10.0",
"crypto-js": "^4.0.0",
"dom-to-image": "^2.6.0",
"howler": "^2.2.3",
@@ -72,6 +73,7 @@
"libphonenumber-js": "^1.9.22",
"md5-typescript": "^1.0.5",
"moment": "^2.29.1",
+ "ng-apexcharts": "^3.0.0",
"ng-packagr": "^12.0.0",
"ngx-device-detector": "^2.1.1",
"ngx-markdown": "^12.0.1",
diff --git a/projects/x-framework-components/src/lib/x-card/x-card.typings.ts b/projects/x-framework-components/src/lib/x-card/x-card.typings.ts
index 3f46076..13887b8 100644
--- a/projects/x-framework-components/src/lib/x-card/x-card.typings.ts
+++ b/projects/x-framework-components/src/lib/x-card/x-card.typings.ts
@@ -2,14 +2,12 @@ import {
Input,
Inject,
NgZone,
- Output,
Component,
Renderer2,
ElementRef,
Injectable,
- EventEmitter,
- ChangeDetectorRef,
TemplateRef,
+ ChangeDetectorRef,
} from '@angular/core';
import {
XStandardType,
diff --git a/projects/x-framework-components/src/lib/x-chart/x-chart.component.html b/projects/x-framework-components/src/lib/x-chart/x-chart.component.html
new file mode 100644
index 0000000..8e7d5c3
--- /dev/null
+++ b/projects/x-framework-components/src/lib/x-chart/x-chart.component.html
@@ -0,0 +1,44 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/projects/x-framework-components/src/lib/x-chart/x-chart.component.scss b/projects/x-framework-components/src/lib/x-chart/x-chart.component.scss
new file mode 100644
index 0000000..ddc4b4d
--- /dev/null
+++ b/projects/x-framework-components/src/lib/x-chart/x-chart.component.scss
@@ -0,0 +1,10 @@
+.x-chart-wrapper {
+ width: 100%;
+ height: 100%;
+ min-height: 300px; // ارتفاع پیشفرض
+}
+
+.x-chart-container {
+ width: 100%;
+ height: 100%;
+}
diff --git a/projects/x-framework-components/src/lib/x-chart/x-chart.component.ts b/projects/x-framework-components/src/lib/x-chart/x-chart.component.ts
new file mode 100644
index 0000000..6280a34
--- /dev/null
+++ b/projects/x-framework-components/src/lib/x-chart/x-chart.component.ts
@@ -0,0 +1,282 @@
+import {
+ Component,
+ ViewChild,
+ ElementRef,
+ ChangeDetectionStrategy,
+} from '@angular/core';
+import { Subscription } from 'rxjs';
+import { isNullOrUndefined, nameof } from 'x-framework-core';
+import ApexCharts, { ApexAxisChartSeries, ApexOptions } from 'apexcharts';
+import { XChartAction, XChartContainerComponent } from './x-chart.typings';
+
+@Component({
+ selector: 'x-chart',
+ templateUrl: './x-chart.component.html',
+ styleUrls: ['./x-chart.component.scss'],
+ changeDetection: ChangeDetectionStrategy.OnPush,
+})
+export class XChartComponent extends XChartContainerComponent {
+ //
+ //#region Props ...
+ //
+ @ViewChild('chartContainer', { static: true })
+ chartContainer!: ElementRef;
+
+ //
+ private chartInstance: ApexCharts | null = null;
+
+ //
+ private ACTION_PROVIDER_SUBSCRIPTION: Subscription;
+ //#endregion
+
+ //
+ //#region LifeCycles ...
+ afterViewInit() {
+ super.afterViewInit();
+
+ //
+ this.initializeComponent();
+ }
+
+ async onChange(changeKeys: string[]) {
+ super.onChange(changeKeys);
+
+ //
+ // Auto Update ...
+ const isAutoUpdateChanged = changeKeys.includes(
+ nameof('autoUpdate'),
+ );
+ if (isAutoUpdateChanged) {
+ //
+ if (isNullOrUndefined(this.autoUpdate)) {
+ this.autoUpdate = this.propertyProvider.autoUpdate;
+ }
+ }
+
+ //
+ // Check Auto Update ...
+ var autoUpdate = await this.getValueAsync(this.autoUpdate);
+ if (!autoUpdate || isNullOrUndefined(this.chartInstance)) {
+ return;
+ }
+
+ //
+ // Options Changed ...
+ const isOptionsChanged = changeKeys.includes(
+ nameof('options'),
+ );
+ if (isOptionsChanged) {
+ //
+ if (isNullOrUndefined(this.options)) {
+ this.options = this.propertyProvider.options;
+ }
+
+ //
+ const options = await this.getValueAsync(this.options);
+ await this.chartInstance.updateOptions(options);
+ }
+
+ //
+ // Series Changed ...
+ const isSeriesChanged = changeKeys.includes(
+ nameof('series'),
+ );
+ if (isSeriesChanged) {
+ //
+ if (isNullOrUndefined(this.series)) {
+ this.series = this.propertyProvider.series;
+ }
+
+ //
+ const series = await this.getValueAsync(this.series);
+ await this.chartInstance.updateSeries(series);
+ }
+
+ //
+ // Action Provider Changed ...
+ const isActionProviderChanged = changeKeys.includes(
+ nameof('actionProvider'),
+ );
+ if (isActionProviderChanged) {
+ this.registerActionProvider();
+ }
+
+ //
+ this.detectChanges();
+ }
+
+ onDestroy() {
+ super.onDestroy();
+
+ //
+ if (!isNullOrUndefined(this.chartInstance)) {
+ //
+ this.chartInstance.destroy();
+ this.chartInstance = null;
+ }
+
+ //
+ this.unregisterActionProvider();
+ }
+ //#endregion
+
+ //
+ //#region Private ...
+ private async initializeComponent() {
+ //
+ // Reading Requirement Properties ...
+ const chartId = await this.getValueAsync(this.uuid);
+ const series = await this.getValueAsync(this.series);
+ const options = await this.getValueAsync(this.options);
+
+ //
+ // Validate Options ...
+ if (isNullOrUndefined(options)) {
+ return;
+ }
+
+ //
+ const computedStyle = getComputedStyle(document.documentElement);
+ const primaryColor =
+ computedStyle.getPropertyValue('--x-color-primary').trim() || '#008ffb';
+ const textColor =
+ computedStyle.getPropertyValue('--x-color-dark-contrast').trim() ||
+ '#373d3f';
+ const gridColor =
+ computedStyle.getPropertyValue('--x-color-light-shade').trim() ||
+ '#e0e0e0';
+ const finalOptions: ApexOptions = {
+ ...options,
+ series: series as any,
+ chart: {
+ ...options.chart,
+ id: chartId,
+ type: options.chart?.type || 'line',
+ foreColor: textColor,
+ events: {
+ ...options.chart?.events,
+ mounted: (chartContext, config) => {
+ this.chartRendered.emit();
+ options.chart?.events?.mounted?.(chartContext, config);
+ },
+ dataPointSelection: (event, chartContext, config) => {
+ this.dataPointSelected.emit({ event, chartContext, config });
+ options.chart?.events?.dataPointSelection?.(
+ event,
+ chartContext,
+ config,
+ );
+ },
+ zoomed: (chartContext, { xaxis, yaxis }) => {
+ this.zoomed.emit({ xaxis, yaxis });
+ options.chart?.events?.zoomed?.(chartContext, { xaxis, yaxis });
+ },
+ },
+ },
+ theme: {
+ ...options.theme,
+ palette: options.theme?.palette || 'palette1',
+ },
+ colors: options.colors || [primaryColor],
+ grid: {
+ ...options.grid,
+ borderColor: gridColor,
+ },
+ xaxis: {
+ ...options.xaxis,
+ labels: {
+ ...options.xaxis?.labels,
+ style: { colors: textColor },
+ },
+ },
+ yaxis: {
+ ...options.yaxis,
+ labels: {
+ ...(options.yaxis as any).labels,
+ style: { colors: textColor },
+ },
+ },
+ };
+
+ //
+ this.chartInstance = new ApexCharts(
+ this.chartContainer.nativeElement,
+ finalOptions,
+ );
+
+ //
+ await this.chartInstance.render();
+ }
+
+ private registerActionProvider() {
+ //
+ this.unregisterActionProvider();
+
+ //
+ if (!this.actionProvider) {
+ this.actionProvider = this.propertyProvider.actionProvider;
+ }
+
+ //
+ this.ACTION_PROVIDER_SUBSCRIPTION = this.actionProvider
+ .asObservable()
+ .subscribe(async (model) => {
+ //
+ switch (model.action) {
+ //
+ case XChartAction.Refresh:
+ this.detectChanges();
+ break;
+
+ //
+ case XChartAction.Destroy:
+ this.onDestroy();
+ break;
+
+ //
+ case XChartAction.UpdateSeries:
+ //
+ if (
+ !isNullOrUndefined(model.payload) ||
+ !isNullOrUndefined(this.chartInstance)
+ ) {
+ //
+ this.series = await this.getValueAsync(
+ model.payload as ApexAxisChartSeries,
+ );
+ await this.chartInstance.updateSeries(this.series);
+ this.detectChanges();
+ }
+ break;
+
+ //
+ case XChartAction.UpdateOptions:
+ //
+ if (
+ !isNullOrUndefined(model.payload) ||
+ !isNullOrUndefined(this.chartInstance)
+ ) {
+ //
+ this.options = await this.getValueAsync(
+ model.payload as ApexOptions,
+ );
+ await this.chartInstance.updateOptions(this.options);
+ this.detectChanges();
+ }
+ break;
+ }
+ });
+ }
+
+ private unregisterActionProvider() {
+ //
+ if (!this.ACTION_PROVIDER_SUBSCRIPTION) {
+ return;
+ }
+
+ //
+ this.ACTION_PROVIDER_SUBSCRIPTION.unsubscribe();
+ this.ACTION_PROVIDER_SUBSCRIPTION = null;
+ }
+ //#endregion
+}
diff --git a/projects/x-framework-components/src/lib/x-chart/x-chart.module.ts b/projects/x-framework-components/src/lib/x-chart/x-chart.module.ts
new file mode 100644
index 0000000..c63a6af
--- /dev/null
+++ b/projects/x-framework-components/src/lib/x-chart/x-chart.module.ts
@@ -0,0 +1,29 @@
+import { NgModule } from '@angular/core';
+import { CommonModule } from '@angular/common';
+import { XCardModule } from '../x-card/x-card.module';
+import { XIconModule } from '../x-icon/x-icon.module';
+import { XChartComponent } from './x-chart.component';
+import { XSlotterModule } from '../x-slotter/x-slotter.module';
+import { XBaseComponentModule } from '../modules/x-base-component.module';
+
+@NgModule({
+ providers: [],
+ entryComponents: [],
+ declarations: [XChartComponent],
+ imports: [
+ XCardModule,
+ XIconModule,
+ CommonModule,
+ XSlotterModule,
+ XBaseComponentModule,
+ ],
+ exports: [
+ XCardModule,
+ XIconModule,
+ CommonModule,
+ XSlotterModule,
+ XChartComponent,
+ XBaseComponentModule,
+ ],
+})
+export class XChartModule {}
diff --git a/projects/x-framework-components/src/lib/x-chart/x-chart.typings.ts b/projects/x-framework-components/src/lib/x-chart/x-chart.typings.ts
new file mode 100644
index 0000000..fdb9eac
--- /dev/null
+++ b/projects/x-framework-components/src/lib/x-chart/x-chart.typings.ts
@@ -0,0 +1,194 @@
+import {
+ ApexOptions,
+ ApexAxisChartSeries,
+ ApexNonAxisChartSeries,
+} from 'apexcharts';
+import {
+ Input,
+ Inject,
+ NgZone,
+ Output,
+ Component,
+ Renderer2,
+ ElementRef,
+ Injectable,
+ EventEmitter,
+ ChangeDetectorRef,
+} from '@angular/core';
+import {
+ IXComponentCardContainer,
+ XComponentCardContainer,
+ XComponentCardContainerComponent,
+} from '../x-card/x-card.typings';
+import { concatMap, map } from 'rxjs/operators';
+import { forkJoin, Observable, Subject } from 'rxjs';
+import { ViewportRuler } from '@angular/cdk/overlay';
+import { XManagerService } from 'x-framework-services';
+import { XOneOrManyType, XStandardType } from 'x-framework-core';
+import { XBaseActionModel } from '../classes/x-base-action.model';
+import { X_FRAMEWORK_COMPONENTS_CONFIG } from '../tokens/x-injectable-tokens';
+import { XFrameworkComponentsConfig } from '../config/x-framework-components.config';
+
+export enum XChartAction {
+ Refresh = 'refresh',
+ Destroy = 'destroy',
+ UpdateSeries = 'update_series',
+ UpdateOptions = 'update_options',
+}
+
+export type XChartActionIdentifier = XChartAction | string;
+
+export interface XChartActionModel extends XBaseActionModel {}
+
+export interface IXChartContainer extends IXComponentCardContainer {
+ //
+ // Commons ...
+ actionProvider: Subject;
+ cssClass: XStandardType>;
+
+ //
+ // Chart Props ...
+ autoUpdate: XStandardType;
+ options: XStandardType;
+ series: XStandardType;
+
+ //
+ // Events ...
+ chartRendered: EventEmitter;
+ dataPointSelected: EventEmitter;
+ zoomed: EventEmitter;
+}
+
+@Injectable({
+ providedIn: 'root',
+})
+export class XChartContainer
+ extends XComponentCardContainer
+ implements Readonly
+{
+ //
+ readonly chartId: XStandardType;
+ readonly options: XStandardType;
+ readonly autoUpdate: XStandardType = true;
+ readonly cssClass: XStandardType>;
+ readonly actionProvider = new Subject();
+ readonly series: XStandardType;
+
+ //
+ readonly zoomed = new EventEmitter();
+ readonly chartRendered = new EventEmitter();
+ readonly dataPointSelected = new EventEmitter();
+}
+
+@Injectable()
+@Component({ template: `` })
+export abstract class XChartContainerComponent
+ extends XComponentCardContainerComponent
+ implements IXChartContainer
+{
+ //
+ //#region Props ...
+ @Input()
+ actionProvider: Subject =
+ this.propertyProvider.actionProvider;
+
+ @Input()
+ cssClass: XStandardType> =
+ this.propertyProvider.cssClass;
+
+ @Input()
+ options: XStandardType = this.propertyProvider.options;
+
+ @Input()
+ series: XStandardType =
+ this.propertyProvider.series;
+
+ @Input()
+ autoUpdate: XStandardType = this.propertyProvider.autoUpdate;
+
+ //
+ // Events ...
+ @Output()
+ chartRendered: EventEmitter = new EventEmitter();
+
+ @Output()
+ dataPointSelected: EventEmitter = new EventEmitter();
+
+ @Output()
+ zoomed: EventEmitter = new EventEmitter();
+ //#endregion
+
+ //
+ //#region Constructor ...
+ constructor(
+ @Inject(X_FRAMEWORK_COMPONENTS_CONFIG)
+ public config: XFrameworkComponentsConfig,
+ public managerService: XManagerService,
+ public element: ElementRef,
+ public renderer: Renderer2,
+ public ruler: ViewportRuler,
+ public zone: NgZone,
+ public changeDetector: ChangeDetectorRef,
+ protected propertyProvider: XChartContainer,
+ ) {
+ super(
+ config,
+ managerService,
+ element,
+ renderer,
+ ruler,
+ zone,
+ changeDetector,
+ propertyProvider,
+ );
+ }
+ //#endregionّ
+
+ //
+ //#region Abstract ...
+ public extractProps(
+ container?: IXChartContainer,
+ ): Observable {
+ return super
+ .extractProps(container ? container : this)
+ .pipe(
+ concatMap((superData) => {
+ return forkJoin({
+ //
+ cssClass: this.getArrayValue(
+ container ? container.cssClass : this.cssClass,
+ ),
+
+ //
+ options: this.getValue(
+ container ? container.options : this.options,
+ ),
+ series: this.getValue(container ? container.series : this.series),
+ autoUpdate: this.getValue(
+ container ? container.autoUpdate : this.autoUpdate,
+ ),
+ }).pipe(
+ map((data) => {
+ return {
+ superData,
+ data,
+ };
+ }),
+ );
+ }),
+ )
+ .pipe(
+ map((info) => ({
+ ...info.data,
+ ...info.superData,
+ actionProvider: container
+ ? container.actionProvider
+ : this.actionProvider,
+ chartRendered: this.chartRendered,
+ dataPointSelected: this.dataPointSelected,
+ zoomed: this.zoomed,
+ })),
+ );
+ }
+ //#endregion
+}
diff --git a/projects/x-framework-components/src/lib/x-framework-components.module.ts b/projects/x-framework-components/src/lib/x-framework-components.module.ts
index ee2ab6c..f8368a7 100644
--- a/projects/x-framework-components/src/lib/x-framework-components.module.ts
+++ b/projects/x-framework-components/src/lib/x-framework-components.module.ts
@@ -18,6 +18,7 @@ import { XTableModule } from './x-table/x-table.module';
import { XFrameworkCoreModule } from 'x-framework-core';
import { XThemeModule } from './x-theme/x-theme.module';
import { XColorModule } from './x-color/x-color.module';
+import { XChartModule } from './x-chart/x-chart.module';
import { XSliderModule } from './x-slider/x-slider.module';
import { XButtonModule } from './x-button/x-button.module';
import { XDialogsModule } from './dialogs/x-dialog.module';
@@ -77,6 +78,7 @@ import { XDescriptionListModule } from './x-description-list/x-description-list.
XListModule,
XTabsModule,
XGridModule,
+ XChartModule,
XFrameModule,
XEmptyModule,
XColorModule,
@@ -136,6 +138,7 @@ import { XDescriptionListModule } from './x-description-list/x-description-list.
XTabsModule,
XGridModule,
XEmptyModule,
+ XChartModule,
XFrameModule,
XColorModule,
XTableModule,
@@ -166,4 +169,4 @@ import { XDescriptionListModule } from './x-description-list/x-description-list.
XDescriptionListModule,
],
})
-export class XFrameworkComponentsModule { }
+export class XFrameworkComponentsModule {}
diff --git a/projects/x-framework-components/src/public-api.ts b/projects/x-framework-components/src/public-api.ts
index b0585c3..4c2aa58 100644
--- a/projects/x-framework-components/src/public-api.ts
+++ b/projects/x-framework-components/src/public-api.ts
@@ -17,6 +17,7 @@ export * from './lib/x-grid/x-grid.module';
export * from './lib/x-page/x-page.module';
export * from './lib/x-tabs/x-tabs.module';
export * from './lib/x-tree/x-tree.module';
+export * from './lib/x-chart/x-chart.module';
export * from './lib/x-theme/x-theme.module';
export * from './lib/x-table/x-table.module';
export * from './lib/x-frame/x-frame.module';
@@ -62,6 +63,7 @@ export * from './lib/x-grid/x-grid.component';
export * from './lib/x-page/x-page.component';
export * from './lib/x-tabs/x-tabs.component';
export * from './lib/x-tree/x-tree.component';
+export * from './lib/x-chart/x-chart.component';
export * from './lib/x-color/x-color.component';
export * from './lib/x-empty/x-empty.component';
export * from './lib/x-frame/x-frame.component';
@@ -123,6 +125,7 @@ export * from './lib/x-tree/x-tree.typings';
export * from './lib/x-empty/x-empty.typings';
export * from './lib/x-frame/x-frame.typings';
export * from './lib/x-page/x-page.container';
+export * from './lib/x-chart/x-chart.typings';
export * from './lib/x-color/x-color.typings';
export * from './lib/x-theme/x-theme-typings';
export * from './lib/x-table/x-table.typings';
diff --git a/tsconfig.json b/tsconfig.json
index ca50783..0815ceb 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -19,6 +19,7 @@
"moduleResolution": "node",
"experimentalDecorators": true,
"noFallthroughCasesInSwitch": true,
+ "allowSyntheticDefaultImports": true,
"suppressImplicitAnyIndexErrors": true,
"forceConsistentCasingInFileNames": true,
"lib": [