fix: force chart re-render when style/bindings change

Use JSON.stringify as useMemo key to detect deep changes in chart
config. Also removed redundant ensureStyle (now handled in Redux).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
hailin 2026-04-05 03:00:36 -07:00
parent e12f169319
commit 7e9957806e
1 changed files with 15 additions and 40 deletions

View File

@ -2,66 +2,41 @@
import React, { useMemo } from 'react'; import React, { useMemo } from 'react';
import { type ChartInstance } from '@/domain/entities/ChartInstance'; import { type ChartInstance } from '@/domain/entities/ChartInstance';
import { type StyleConfig } from '@/domain/entities/StyleConfig';
import { chartRenderer } from '@/frameworks/di/container'; import { chartRenderer } from '@/frameworks/di/container';
import { EChartsBase } from './EChartsBase'; import { EChartsBase } from './EChartsBase';
import { KPICard } from './KPICard'; import { KPICard } from './KPICard';
import { DataTable } from './DataTable'; import { DataTable } from './DataTable';
const DEFAULT_STYLE: StyleConfig = {
title: { text: '', visible: true, fontSize: 16, fontWeight: 'bold', color: '#333', align: 'left' },
colors: ['#5470c6', '#91cc75', '#fac858', '#ee6666', '#73c0de', '#3ba272', '#fc8452', '#9a60b4'],
legend: { visible: true, position: 'top', orient: 'horizontal', fontSize: 12, color: '#666' },
xAxis: { visible: true, labelVisible: true, labelFontSize: 12, labelColor: '#666', labelRotation: 0, titleVisible: false, titleText: '', gridVisible: true, gridColor: '#eee' },
yAxis: { visible: true, labelVisible: true, labelFontSize: 12, labelColor: '#666', labelRotation: 0, titleVisible: false, titleText: '', gridVisible: true, gridColor: '#eee' },
dataLabel: { visible: false, fontSize: 12, color: '#333', position: 'outside', format: 'value' },
background: { color: '#fff', opacity: 1, borderRadius: 4 },
border: { visible: false, color: '#ddd', width: 1, style: 'solid' },
animation: { enabled: true, duration: 500, easing: 'ease-out' },
};
function ensureStyle(style: any): StyleConfig {
if (!style || typeof style !== 'object') return DEFAULT_STYLE;
return {
title: { ...DEFAULT_STYLE.title, ...(style.title ?? {}) },
colors: style.colors ?? DEFAULT_STYLE.colors,
legend: { ...DEFAULT_STYLE.legend, ...(style.legend ?? {}) },
xAxis: { ...DEFAULT_STYLE.xAxis, ...(style.xAxis ?? {}) },
yAxis: { ...DEFAULT_STYLE.yAxis, ...(style.yAxis ?? {}) },
dataLabel: { ...DEFAULT_STYLE.dataLabel, ...(style.dataLabel ?? {}) },
background: { ...DEFAULT_STYLE.background, ...(style.background ?? {}) },
border: { ...DEFAULT_STYLE.border, ...(style.border ?? {}) },
animation: { ...DEFAULT_STYLE.animation, ...(style.animation ?? {}) },
};
}
export interface ChartRendererProps { export interface ChartRendererProps {
chart: ChartInstance; chart: ChartInstance;
data: Record<string, any>[]; data: Record<string, any>[];
} }
export const ChartRenderer: React.FC<ChartRendererProps> = ({ chart, data }) => { export const ChartRenderer: React.FC<ChartRendererProps> = ({ chart, data }) => {
const safeChart = useMemo(() => ({ // Use JSON key to force recalculation when any chart property changes
...chart, const chartKey = JSON.stringify({ type: chart.type, bindings: chart.bindings, style: chart.style });
style: ensureStyle(chart.style),
}), [chart]);
const echartsOption = useMemo(() => { const echartsOption = useMemo(() => {
if (safeChart.type === 'kpi' || safeChart.type === 'data-table') return null; if (chart.type === 'kpi' || chart.type === 'data-table') return null;
if (!data.length) return null;
try { try {
return chartRenderer.build(safeChart, data); return chartRenderer.build(chart, data);
} catch { } catch (e) {
console.warn('ECharts build error:', e);
return null; return null;
} }
}, [safeChart, data]); // eslint-disable-next-line react-hooks/exhaustive-deps
}, [chartKey, data]);
switch (safeChart.type) { switch (chart.type) {
case 'kpi': case 'kpi':
return <KPICard chart={safeChart} data={data} />; return <KPICard chart={chart} data={data} />;
case 'data-table': case 'data-table':
return <DataTable chart={safeChart} data={data} />; return <DataTable chart={chart} data={data} />;
default: default:
return echartsOption ? <EChartsBase option={echartsOption} /> : <div style={{ padding: 16, color: '#999' }}></div>; return echartsOption
? <EChartsBase option={echartsOption} style={{ width: '100%', height: '100%', minHeight: 250 }} />
: <div style={{ padding: 16, color: '#999', textAlign: 'center' }}></div>;
} }
}; };