@@ -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