first implementation of x-chart component ...

This commit is contained in:
2026-08-20 15:48:57 +03:30
parent 8ab1322222
commit 203c09a0e8
10 changed files with 570 additions and 4 deletions
@@ -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<XChartComponent>('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<XChartComponent>('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<XChartComponent>('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<XChartComponent>('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
}