Files
dees-catalog/ts_web/elements/00group-chart/dees-chart-echarts-base.ts

113 lines
3.5 KiB
TypeScript

import {
DeesElement,
property,
html,
type TemplateResult,
} from '@design.estate/dees-element';
import * as domtools from '@design.estate/dees-domtools';
import { DeesServiceLibLoader, type IEchartsBundle, type IEchartsInstance } from '../../services/index.js';
import { getEchartsThemeOptions } from './dees-chart-echarts-theme.js';
import '../00group-layout/dees-tile/dees-tile.js';
/**
* Abstract base class for ECharts-based chart components.
* Handles library loading, chart lifecycle, resize observation, and theme switching.
* Subclasses implement `buildOption()` to define their chart configuration.
*/
export abstract class DeesChartEchartsBase extends DeesElement {
@property()
accessor label: string = 'Untitled Chart';
protected chartInstance: IEchartsInstance | null = null;
protected echartsBundle: IEchartsBundle | null = null;
private resizeObserver: ResizeObserver | null = null;
constructor() {
super();
domtools.elementBasic.setup();
this.registerGarbageFunction(async () => {
if (this.resizeObserver) {
this.resizeObserver.disconnect();
this.resizeObserver = null;
}
if (this.chartInstance) {
try {
this.chartInstance.dispose();
this.chartInstance = null;
} catch (e) {
console.error('Error disposing ECharts instance:', e);
}
}
});
}
public render(): TemplateResult {
return html`
<dees-tile>
<div slot="header" class="chartHeader">
<span class="chartLabel">${this.label}</span>
</div>
<div class="chartContainer"></div>
</dees-tile>
`;
}
public async firstUpdated() {
await this.domtoolsPromise;
this.echartsBundle = await DeesServiceLibLoader.getInstance().loadEcharts();
await new Promise(resolve => requestAnimationFrame(resolve));
const chartContainer = this.shadowRoot!.querySelector('.chartContainer') as HTMLDivElement;
if (!chartContainer) return;
try {
this.chartInstance = this.echartsBundle.init(chartContainer, null, { renderer: 'svg' });
this.updateChart();
this.resizeObserver = new ResizeObserver(() => {
this.chartInstance?.resize();
});
this.resizeObserver.observe(chartContainer);
} catch (error) {
console.error('Failed to initialize ECharts:', error);
}
}
public async updated(changedProperties: Map<string, any>) {
super.updated(changedProperties);
if (changedProperties.has('goBright') && this.chartInstance) {
this.applyTheme();
}
}
protected abstract buildOption(): Record<string, any>;
protected updateChart(): void {
if (!this.chartInstance) return;
const themeOptions = getEchartsThemeOptions(this.goBright);
const chartOption = this.buildOption();
// Deep-merge theme defaults with chart-specific options for nested objects
const merged = {
...themeOptions,
...chartOption,
textStyle: { ...themeOptions.textStyle, ...(chartOption.textStyle || {}) },
tooltip: { ...themeOptions.tooltip, ...(chartOption.tooltip || {}) },
legend: {
...themeOptions.legend,
...(chartOption.legend || {}),
textStyle: { ...(themeOptions.legend?.textStyle || {}), ...(chartOption.legend?.textStyle || {}) },
},
};
this.chartInstance.setOption(merged, true);
}
protected applyTheme(): void {
this.updateChart();
}
public async forceResize(): Promise<void> {
this.chartInstance?.resize();
}
}