@@ -279,7 +279,7 @@ <h3 style="text-align:center;">Jitter</h3>
279279
280280/**** ---------- GENERIC LINE-CHART BUILDER ---------- ****/
281281function makeLineChart ( id , datasets , yLabel , yOpts = { } ) {
282- const ctx = document . getElementById ( id ) ;
282+ const ctx = document . getElementById ( id ) ;
283283 const chart = new Chart ( ctx , {
284284 type : 'line' ,
285285 data : { datasets } ,
@@ -306,29 +306,40 @@ <h3 style="text-align:center;">Jitter</h3>
306306 }
307307 } ) ;
308308
309- // Apply pan/zoom handlers and then separately inject limits
309+ // first draw
310+ chart . update ( ) ;
311+
312+ // --- DYNAMIC LIMITS BASED ON DATA MAXIMA ---
313+ // flatten all X and Y values from our datasets
314+ const xVals = datasets . flatMap ( ds => ds . data . map ( pt => pt . x ) ) ;
315+ const yVals = datasets . flatMap ( ds => ds . data . map ( pt => pt . y ) ) ;
316+ const xMax = Math . max ( ...xVals , 0 ) ;
317+ const yMax = Math . max ( ...yVals , 0.1 ) ;
318+
310319 chart . options . plugins . zoom = {
311320 ...fullscreenZoom ( chart ) ,
312321 limits : {
313322 x : {
314- min : 0 ,
315- max : 600 ,
323+ min : 0 ,
324+ max : xMax * 2 ,
316325 minRange : 1 ,
317- maxRange : 600
326+ maxRange : xMax * 2
318327 } ,
319328 y : {
320- min : 0.1 ,
321- max : 300 ,
329+ min : 0.1 ,
330+ max : yMax * 2 ,
322331 minRange : 1 ,
323- maxRange : 300
332+ maxRange : yMax * 2
324333 }
325334 }
326335 } ;
327336
337+ // re-draw with new limits
328338 chart . update ( ) ;
329339 addResetZoomButton ( chart , ctx . parentElement ) ;
330340}
331341
342+
332343/**** ---------- METRIC CHARTS ---------- ****/
333344makeLineChart ( 'hrChart' , dictToDatasets ( heartData ) , 'BPM' ) ;
334345makeLineChart ( 'hrvChart' , dictToDatasets ( hrvData ) , 'ms' ) ;
@@ -451,72 +462,56 @@ <h3 style="text-align:center;">Jitter</h3>
451462
452463if ( Object . keys ( clickEvents || { } ) . length ) {
453464 titleEl . textContent = 'Clicks & Sync Markers' ;
454- const actors = Object . keys ( clickEvents ) . sort ( ) ;
465+ const actors = Object . keys ( clickEvents ) . sort ( ) ;
455466 const topData = ( clickEvents [ actors [ 0 ] ] || [ ] ) . map ( t => ( { x : + t , y : bandCenter ( 0 ) } ) ) ;
456467 const bottomData = ( clickEvents [ actors [ 1 ] ] || [ ] ) . map ( t => ( { x : + t , y : bandCenter ( 1 ) } ) ) ;
457468 const syncData = ( syncEvents || [ ] ) . map ( t => ( { x : + t , y : 0.5 } ) ) ;
458469
459470 const legendSets = [
460- {
461- label : `${ actors [ 0 ] || 'P1' } Click` ,
462- showLine : false ,
463- data : topData . length ? topData : [ { x : 0 , y : bandCenter ( 0 ) } ] ,
464- pointRadius : 0 ,
465- borderColor : COLORS [ 0 ]
466- } ,
467- {
468- label : `${ actors [ 1 ] || 'P2' } Click` ,
469- showLine : false ,
470- data : bottomData . length ? bottomData : [ { x : 0 , y : bandCenter ( 1 ) } ] ,
471- pointRadius : 0 ,
472- borderColor : COLORS [ 1 ]
473- } ,
474- {
475- label : 'Sync' ,
476- showLine : false ,
477- data : syncData . length ? syncData : [ { x : 0 , y : 0.5 } ] ,
478- pointRadius : 0 ,
479- borderColor : SYNC_COLOR
480- }
471+ { label : `${ actors [ 0 ] || 'P1' } Click` , showLine : false , data : topData , pointRadius : 0 , borderColor : COLORS [ 0 ] } ,
472+ { label : `${ actors [ 1 ] || 'P2' } Click` , showLine : false , data : bottomData , pointRadius : 0 , borderColor : COLORS [ 1 ] } ,
473+ { label : 'Sync' , showLine : false , data : syncData , pointRadius : 0 , borderColor : SYNC_COLOR }
481474 ] ;
482475
483- const gameCtx = document . getElementById ( 'gameChart' ) ;
476+ const gameCtx = document . getElementById ( 'gameChart' ) ;
484477 const gameChart = new Chart ( gameCtx , {
485478 type : 'scatter' ,
486479 data : { datasets : legendSets } ,
487480 options : {
488481 parsing : false ,
489482 responsive : true ,
490483 scales : {
491- x : {
492- type : 'linear' ,
493- title : { display : true , text : 'Time (s)' }
494- } ,
495- y : {
496- display : false ,
497- min : 0 ,
498- max : 1
499- }
484+ x : { type : 'linear' , title : { display : true , text : 'Time (s)' } } ,
485+ y : { display : false , min : 0 , max : 1 }
500486 } ,
501487 plugins : {
502488 tooltip : { enabled : false } ,
503- legend : { labels : { usePointStyle : true } }
489+ legend : { labels : { usePointStyle : true } }
504490 }
505491 }
506492 } ) ;
507493
494+ // compute the true max timestamp across all three series
495+ const xMax = Math . max (
496+ ...topData . map ( pt => pt . x ) ,
497+ ...bottomData . map ( pt => pt . x ) ,
498+ ...syncData . map ( pt => pt . x )
499+ ) ;
500+
508501 gameChart . options . plugins . zoom = {
509502 ...fullscreenZoom ( gameChart ) ,
510503 limits : {
511504 x : {
512- min : 0 ,
513- max : 600 ,
505+ min : 0 ,
506+ max : xMax * 2 , // ← now dynamic
514507 minRange : 1 ,
515- maxRange : 60
508+ maxRange : xMax * 2
516509 } ,
517510 y : {
518- min : 0 ,
519- max : 1
511+ min : 0 ,
512+ max : 1 ,
513+ minRange : 0.1 ,
514+ maxRange : 2
520515 }
521516 }
522517 } ;
@@ -525,6 +520,7 @@ <h3 style="text-align:center;">Jitter</h3>
525520 addResetZoomButton ( gameChart , gameCtx . parentElement ) ;
526521}
527522
523+
528524else if ( frequencyData && Object . keys ( frequencyData ) . length ) {
529525 titleEl . textContent = 'Finger Frequency & Sync Intervals' ;
530526 const freqSets = Object . keys ( frequencyData ) . map ( a => ( {
0 commit comments