Skip to content

Commit 8e3f472

Browse files
authored
fix: preserve chart zoom state across data refetches (#382)
Store dataZoom range in component state instead of ref and include start/end values directly in the ECharts option config. This ensures zoom state survives option updates when react-query refetches data or when legend toggles change displayed series. - Changed dataZoomStateRef to zoomRange state - Added threshold check to prevent state update loops - Included start/end in both inside and slider dataZoom configs - Removed obsolete dispatchAction restoration effect
1 parent 2fb9b10 commit 8e3f472

File tree

1 file changed

+19
-25
lines changed

1 file changed

+19
-25
lines changed

src/components/Charts/MultiLine/MultiLine.tsx

Lines changed: 19 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -99,8 +99,9 @@ export function MultiLineChart({
9999
const chartWrapperRef = useRef<ReactEChartsCore | null>(null);
100100
// Store the chart instance for sync cleanup
101101
const chartInstanceRef = useRef<EChartsInstance | null>(null);
102-
// Store dataZoom state to preserve zoom when legend toggles series visibility
103-
const dataZoomStateRef = useRef<{ start: number; end: number } | null>(null);
102+
// Store dataZoom state to preserve zoom across data updates and legend toggles
103+
// Using state (not ref) ensures zoom range is included in the option config
104+
const [zoomRange, setZoomRange] = useState({ start: 0, end: 100 });
104105

105106
// Get shared crosshairs context for sync registration
106107
const crosshairsContext = useContext(SharedCrosshairsContext);
@@ -114,16 +115,24 @@ export function MultiLineChart({
114115
crosshairsContext.registerChart(syncGroup, echartsInstance);
115116
}
116117

117-
// Track dataZoom changes to preserve zoom state when legend toggles series
118-
// Must be attached here because chartInstanceRef is not available during effect mount
118+
// Track dataZoom changes to preserve zoom state across data updates and legend toggles
119+
// Using state ensures the zoom range is included in the option config on re-renders
119120
if (enableDataZoom) {
120121
const handleDataZoom = (): void => {
121122
const option = echartsInstance.getOption() as {
122123
dataZoom?: Array<{ start?: number; end?: number }>;
123124
};
124125
const dz = option.dataZoom?.[0];
125126
if (dz && typeof dz.start === 'number' && typeof dz.end === 'number') {
126-
dataZoomStateRef.current = { start: dz.start, end: dz.end };
127+
const newStart = dz.start;
128+
const newEnd = dz.end;
129+
// Only update state if values changed significantly (prevents infinite loops)
130+
setZoomRange(prev => {
131+
if (Math.abs(prev.start - newStart) < 0.01 && Math.abs(prev.end - newEnd) < 0.01) {
132+
return prev; // No change, return same reference
133+
}
134+
return { start: newStart, end: newEnd };
135+
});
127136
}
128137
};
129138
echartsInstance.on('datazoom', handleDataZoom);
@@ -368,26 +377,6 @@ export function MultiLineChart({
368377
showAggregate,
369378
]);
370379

371-
// Restore dataZoom state after chart option changes
372-
// This handles: legend toggles, data refetches, any re-render that resets zoom
373-
useEffect(() => {
374-
const instance = chartInstanceRef.current;
375-
if (!instance || !enableDataZoom || !dataZoomStateRef.current) return;
376-
377-
// Use setTimeout to ensure this runs after the chart option update
378-
const timeoutId = setTimeout(() => {
379-
if (dataZoomStateRef.current) {
380-
instance.dispatchAction({
381-
type: 'dataZoom',
382-
start: dataZoomStateRef.current.start,
383-
end: dataZoomStateRef.current.end,
384-
});
385-
}
386-
}, 0);
387-
388-
return () => clearTimeout(timeoutId);
389-
}, [displayedSeries, enableDataZoom]);
390-
391380
// Build complete option
392381
// Memoize based on actual data that should trigger re-animation
393382
const option = useMemo(() => {
@@ -803,13 +792,17 @@ export function MultiLineChart({
803792
zoomOnMouseWheel: true,
804793
moveOnMouseWheel: false,
805794
moveOnMouseMove: true,
795+
start: zoomRange.start,
796+
end: zoomRange.end,
806797
},
807798
{
808799
type: 'slider' as const,
809800
xAxisIndex: 0,
810801
filterMode: 'none' as const,
811802
height: 20,
812803
bottom: 10,
804+
start: zoomRange.start,
805+
end: zoomRange.end,
813806
borderColor: themeColors.border,
814807
backgroundColor: 'transparent',
815808
fillerColor: hexToRgba(themeColors.primary, 0.2),
@@ -849,6 +842,7 @@ export function MultiLineChart({
849842
tooltipTrigger,
850843
tooltipMode,
851844
enableDataZoom,
845+
zoomRange,
852846
connectNulls,
853847
extendedPalette,
854848
relativeSlots,

0 commit comments

Comments
 (0)