1- import React , { useMemo , useState , useRef , useCallback } from 'react' ;
1+ import React , { useMemo , useState , useRef , useCallback , useEffect } from 'react' ;
22import { Line } from 'react-chartjs-2' ;
33import { ResizablePanel } from './ResizablePanel' ;
44import {
@@ -12,6 +12,7 @@ import {
1212 Tooltip ,
1313 Legend ,
1414} from 'chart.js' ;
15+ import zoomPlugin from 'chartjs-plugin-zoom' ;
1516
1617ChartJS . register (
1718 CategoryScale ,
@@ -20,7 +21,8 @@ ChartJS.register(
2021 LineElement ,
2122 Title ,
2223 Tooltip ,
23- Legend
24+ Legend ,
25+ zoomPlugin
2426) ;
2527
2628// Chart wrapper component with error boundary
@@ -83,7 +85,10 @@ export default function ChartContainer({
8385 relativeBaseline = 0.002 ,
8486 absoluteBaseline = 0.005 ,
8587 showLoss = true ,
86- showGradNorm = false
88+ showGradNorm = false ,
89+ xRange = { min : undefined , max : undefined } ,
90+ onXRangeChange,
91+ onMaxStepChange
8792} ) {
8893 // 同步hover状态管理
8994 const [ syncHoverStep , setSyncHoverStep ] = useState ( null ) ;
@@ -306,6 +311,15 @@ export default function ChartContainer({
306311 } ) ;
307312 } , [ files , lossRegex , gradNormRegex ] ) ;
308313
314+ useEffect ( ( ) => {
315+ const maxStep = parsedData . reduce ( ( max , file ) => {
316+ const maxLoss = file . lossData . length > 0 ? file . lossData [ file . lossData . length - 1 ] . x : 0 ;
317+ const maxGrad = file . gradNormData . length > 0 ? file . gradNormData [ file . gradNormData . length - 1 ] . x : 0 ;
318+ return Math . max ( max , maxLoss , maxGrad ) ;
319+ } , 0 ) ;
320+ onMaxStepChange ( maxStep ) ;
321+ } , [ parsedData , onMaxStepChange ] ) ;
322+
309323 const movingAverage = ( data , windowSize = 10 ) => {
310324 return data . map ( ( point , index ) => {
311325 const start = Math . max ( 0 , index - windowSize + 1 ) ;
@@ -365,6 +379,36 @@ export default function ChartContainer({
365379 intersect : false ,
366380 } ,
367381 plugins : {
382+ zoom : {
383+ pan : {
384+ enabled : true ,
385+ mode : 'x' ,
386+ onPanComplete : ( { chart} ) => {
387+ const { min, max} = chart . scales . x ;
388+ onXRangeChange ( { min : Math . round ( min ) , max : Math . round ( max ) } ) ;
389+ }
390+ } ,
391+ zoom : {
392+ drag : {
393+ enabled : true ,
394+ borderColor : 'rgba(225,225,225,0.2)' ,
395+ borderWidth : 1 ,
396+ backgroundColor : 'rgba(225,225,225,0.2)' ,
397+ modifierKey : 'shift' ,
398+ } ,
399+ wheel : {
400+ enabled : true ,
401+ } ,
402+ pinch : {
403+ enabled : true
404+ } ,
405+ mode : 'x' ,
406+ onZoomComplete : ( { chart} ) => {
407+ const { min, max} = chart . scales . x ;
408+ onXRangeChange ( { min : Math . round ( min ) , max : Math . round ( max ) } ) ;
409+ }
410+ }
411+ } ,
368412 legend : {
369413 position : 'top' ,
370414 labels : {
@@ -449,7 +493,8 @@ export default function ChartContainer({
449493 display : true ,
450494 text : 'Step' ,
451495 } ,
452- min : 0 ,
496+ min : xRange . min ,
497+ max : xRange . max ,
453498 bounds : 'data'
454499 } ,
455500 y : {
0 commit comments