@@ -93,6 +93,8 @@ export default function ChartContainer({
9393 files,
9494 metrics = [ ] ,
9595 compareMode,
96+ multiFileMode = 'baseline' ,
97+ baselineFile,
9698 relativeBaseline = 0.002 ,
9799 absoluteBaseline = 0.005 ,
98100 xRange = { min : undefined , max : undefined } ,
@@ -531,40 +533,74 @@ export default function ChartContainer({
531533 elements : { point : { radius : 0 } }
532534 } ) , [ xRange , onXRangeChange ] ) ;
533535
534- const createComparisonChartData = ( item1 , item2 , title ) => {
535- const comparisonData = getComparisonData ( item1 . data , item2 . data , compareMode ) ;
536- const baseline =
536+ const buildComparisonChartData = ( dataArray ) => {
537+ const baselineVal =
537538 compareMode === 'relative' || compareMode === 'relative-normal'
538539 ? relativeBaseline
539540 : compareMode === 'absolute'
540541 ? absoluteBaseline
541542 : 0 ;
542- const datasets = [
543- {
544- label : t ( 'chart.diffLabel' , { title } ) ,
545- data : comparisonData ,
546- borderColor : '#dc2626' ,
547- backgroundColor : '#dc2626' ,
543+ const datasets = [ ] ;
544+ const stats = [ ] ;
545+ const addPair = ( base , target , colorIdx ) => {
546+ const diffData = getComparisonData ( base . data , target . data , compareMode ) ;
547+ const color = colors [ colorIdx % colors . length ] ;
548+ datasets . push ( {
549+ label : `${ target . name } vs ${ base . name } ` ,
550+ data : diffData ,
551+ borderColor : color ,
552+ backgroundColor : color ,
548553 borderWidth : 2 ,
549554 fill : false ,
550555 tension : 0 ,
551556 pointRadius : 0 ,
552557 pointHoverRadius : 4 ,
553- pointBackgroundColor : '#dc2626' ,
554- pointBorderColor : '#dc2626' ,
558+ pointBackgroundColor : color ,
559+ pointBorderColor : color ,
555560 pointBorderWidth : 1 ,
556- pointHoverBackgroundColor : '#dc2626' ,
557- pointHoverBorderColor : '#dc2626' ,
561+ pointHoverBackgroundColor : color ,
562+ pointHoverBorderColor : color ,
558563 pointHoverBorderWidth : 1 ,
559564 animation : false ,
560565 animations : { colors : false , x : false , y : false } ,
561- } ,
562- ] ;
563- if ( baseline > 0 && ( compareMode === 'relative' || compareMode === 'relative-normal' || compareMode === 'absolute' ) ) {
564- const baselineData = comparisonData . map ( p => ( { x : p . x , y : baseline } ) ) ;
566+ } ) ;
567+ const normalDiff = getComparisonData ( base . data , target . data , 'normal' ) ;
568+ const absDiff = getComparisonData ( base . data , target . data , 'absolute' ) ;
569+ const relNormalDiff = getComparisonData ( base . data , target . data , 'relative-normal' ) ;
570+ const relDiff = getComparisonData ( base . data , target . data , 'relative' ) ;
571+ const mean = arr => ( arr . reduce ( ( s , p ) => s + p . y , 0 ) / arr . length ) || 0 ;
572+ const max = arr => arr . reduce ( ( m , p ) => ( p . y > m ? p . y : m ) , 0 ) ;
573+ stats . push ( {
574+ label : `${ target . name } vs ${ base . name } ` ,
575+ meanNormal : mean ( normalDiff ) ,
576+ meanAbsolute : mean ( absDiff ) ,
577+ relativeError : mean ( relNormalDiff ) ,
578+ meanRelative : mean ( relDiff ) ,
579+ maxAbsolute : max ( absDiff ) ,
580+ maxRelative : max ( relDiff )
581+ } ) ;
582+ } ;
583+
584+ let colorIdx = 0 ;
585+ if ( multiFileMode === 'baseline' ) {
586+ const base = dataArray . find ( d => d . name === baselineFile ) || dataArray [ 0 ] ;
587+ dataArray . forEach ( item => {
588+ if ( item . name === base . name ) return ;
589+ addPair ( base , item , colorIdx ++ ) ;
590+ } ) ;
591+ } else {
592+ for ( let i = 0 ; i < dataArray . length ; i ++ ) {
593+ for ( let j = i + 1 ; j < dataArray . length ; j ++ ) {
594+ addPair ( dataArray [ i ] , dataArray [ j ] , colorIdx ++ ) ;
595+ }
596+ }
597+ }
598+
599+ if ( datasets . length > 0 && baselineVal > 0 && ( compareMode === 'relative' || compareMode === 'relative-normal' || compareMode === 'absolute' ) ) {
600+ const baseData = datasets [ 0 ] . data . map ( p => ( { x : p . x , y : baselineVal } ) ) ;
565601 datasets . push ( {
566602 label : 'Baseline' ,
567- data : baselineData ,
603+ data : baseData ,
568604 borderColor : '#10b981' ,
569605 backgroundColor : '#10b981' ,
570606 borderWidth : 2 ,
@@ -583,7 +619,8 @@ export default function ChartContainer({
583619 animations : { colors : false , x : false , y : false } ,
584620 } ) ;
585621 }
586- return { datasets } ;
622+
623+ return { datasets, stats } ;
587624 } ;
588625
589626 if ( parsedData . length === 0 ) {
@@ -626,7 +663,7 @@ export default function ChartContainer({
626663 const metricElements = metrics . map ( ( metric , idx ) => {
627664 const key = metric . name || metric . keyword || `metric${ idx + 1 } ` ;
628665 const dataArray = metricDataArrays [ key ] || [ ] ;
629- const showComparison = dataArray . length == = 2 ;
666+ const showComparison = dataArray . length > = 2 ;
630667
631668 const yRange = calculateYRange ( dataArray ) ;
632669 const options = {
@@ -638,28 +675,11 @@ export default function ChartContainer({
638675 } ;
639676
640677 let stats = null ;
641- if ( showComparison ) {
642- const normalDiff = getComparisonData ( dataArray [ 0 ] . data , dataArray [ 1 ] . data , 'normal' ) ;
643- const absDiff = getComparisonData ( dataArray [ 0 ] . data , dataArray [ 1 ] . data , 'absolute' ) ;
644- const relNormalDiff = getComparisonData (
645- dataArray [ 0 ] . data ,
646- dataArray [ 1 ] . data ,
647- 'relative-normal'
648- ) ;
649- const relDiff = getComparisonData ( dataArray [ 0 ] . data , dataArray [ 1 ] . data , 'relative' ) ;
650- const mean = arr => ( arr . reduce ( ( s , p ) => s + p . y , 0 ) / arr . length ) || 0 ;
651- stats = {
652- meanNormal : mean ( normalDiff ) ,
653- meanAbsolute : mean ( absDiff ) ,
654- relativeError : mean ( relNormalDiff ) ,
655- meanRelative : mean ( relDiff )
656- } ;
657- }
658-
659678 let comparisonChart = null ;
660679 if ( showComparison ) {
661- const compData = createComparisonChartData ( dataArray [ 0 ] , dataArray [ 1 ] , key ) ;
662- const compRange = calculateYRange ( compData . datasets ) ;
680+ const compResult = buildComparisonChartData ( dataArray ) ;
681+ stats = compResult . stats . length > 0 ? compResult . stats : null ;
682+ const compRange = calculateYRange ( compResult . datasets ) ;
663683 const compOptions = {
664684 ...chartOptions ,
665685 scales : {
@@ -705,7 +725,7 @@ export default function ChartContainer({
705725 onRegisterChart = { registerChart }
706726 onSyncHover = { syncHoverToAllCharts }
707727 syncRef = { syncLockRef }
708- data = { compData }
728+ data = { { datasets : compResult . datasets } }
709729 options = { compOptions }
710730 />
711731 </ ResizablePanel >
@@ -760,14 +780,32 @@ export default function ChartContainer({
760780 </ ResizablePanel >
761781 { comparisonChart }
762782 { stats && (
763- < div className = "card" >
764- < h4 className = "text-sm font-medium text-gray-700 dark:text-gray-300 mb-1" > { key } { t ( 'chart.diffStats' ) } </ h4 >
765- < div className = "space-y-1 text-xs" >
766- < p > { t ( 'comparison.meanNormal' , { value : stats . meanNormal . toFixed ( 6 ) } ) } </ p >
767- < p > { t ( 'comparison.meanAbsolute' , { value : stats . meanAbsolute . toFixed ( 6 ) } ) } </ p >
768- < p > { t ( 'comparison.relativeError' , { value : stats . relativeError . toFixed ( 6 ) } ) } </ p >
769- < p > { t ( 'comparison.meanRelative' , { value : stats . meanRelative . toFixed ( 6 ) } ) } </ p >
770- </ div >
783+ < div className = "card overflow-x-auto" >
784+ < h4 className = "text-sm font-medium text-gray-700 dark:text-gray-300 mb-2" > { key } { t ( 'chart.diffStats' ) } </ h4 >
785+ < table className = "min-w-full text-xs" >
786+ < thead >
787+ < tr className = "text-left" >
788+ < th className = "pr-2" > { t ( 'comparison.pair' ) } </ th >
789+ < th className = "text-right" > { t ( 'comparison.meanNormalLabel' ) } </ th >
790+ < th className = "text-right" > { t ( 'comparison.meanAbsoluteLabel' ) } </ th >
791+ < th className = "text-right" > { t ( 'comparison.maxAbsoluteLabel' ) } </ th >
792+ < th className = "text-right" > { t ( 'comparison.meanRelativeLabel' ) } </ th >
793+ < th className = "text-right" > { t ( 'comparison.maxRelativeLabel' ) } </ th >
794+ </ tr >
795+ </ thead >
796+ < tbody >
797+ { stats . map ( s => (
798+ < tr key = { s . label } className = "border-t border-gray-200 dark:border-gray-700" >
799+ < td className = "pr-2 py-1" > { s . label } </ td >
800+ < td className = "text-right py-1" > { s . meanNormal . toFixed ( 6 ) } </ td >
801+ < td className = "text-right py-1" > { s . meanAbsolute . toFixed ( 6 ) } </ td >
802+ < td className = "text-right py-1" > { s . maxAbsolute . toFixed ( 6 ) } </ td >
803+ < td className = "text-right py-1" > { s . meanRelative . toFixed ( 6 ) } </ td >
804+ < td className = "text-right py-1" > { s . maxRelative . toFixed ( 6 ) } </ td >
805+ </ tr >
806+ ) ) }
807+ </ tbody >
808+ </ table >
771809 </ div >
772810 ) }
773811 </ div >
0 commit comments