@@ -30,6 +30,7 @@ public class GraphViewModel : INotifyPropertyChanged
3030 private static readonly string FloatFormat = "0.000" ;
3131 private static readonly int MaxSeriesCount = 4 ;
3232 private static readonly string DefaultSeries2Suffix = " 2" ;
33+ private static readonly int MetricsPrecision = 3 ;
3334
3435 // Y-Axis position options for each series
3536 public static readonly string YAxisLeft = "Left" ;
@@ -48,6 +49,7 @@ public class GraphViewModel : INotifyPropertyChanged
4849 private IEnumerable < ISeries > _series ;
4950 private IEnumerable < ICartesianAxis > _xAxes ;
5051 private IEnumerable < ICartesianAxis > _yAxes ;
52+ private ObservableCollection < SeriesMetrics > _seriesMetrics ;
5153
5254 private string _xAxisLabel ;
5355 private string _yAxisLabel ;
@@ -76,6 +78,7 @@ public GraphViewModel(ImmutableArray<IRecord> records, string regularExpression,
7678 this . SourceFilePath = sourceFilePath ?? string . Empty ;
7779 this . TooltipWidth = 10 ;
7880 this . RegularExpression = regularExpression ?? string . Empty ;
81+ this . SeriesMetrics = new ObservableCollection < SeriesMetrics > ( ) ;
7982
8083 this . SampleData = records . Any ( )
8184 ? _records [ 0 ] . Content
@@ -331,8 +334,20 @@ public string Series4Axis
331334 }
332335 }
333336
337+ public ObservableCollection < SeriesMetrics > SeriesMetrics
338+ {
339+ get => _seriesMetrics ;
340+ set
341+ {
342+ _seriesMetrics = value ;
343+ RaisePropertyChanged ( nameof ( this . SeriesMetrics ) ) ;
344+ }
345+ }
346+
334347 public ICommand UpdateCommand => new UiBoundCommand ( ( ) => Update ( false ) ) ;
335348
349+ public ICommand CopyMetricsCommand => new UiBoundCommand ( ( ) => CopyMetricsToClipboard ( ) ) ;
350+
336351 private string GetDetectedData ( string regularExpression , string inputString )
337352 {
338353 var result = string . Empty ;
@@ -448,6 +463,9 @@ private void Update(bool isInitializing)
448463 string axisName = allNames . Any ( ) ? string . Join ( " / " , allNames ) : DefaultYAxisLabel ;
449464 this . YAxes = GetYAxes ( axisName ) ;
450465 }
466+
467+ // Calculate and update metrics
468+ this . SeriesMetrics = CalculateSeriesMetrics ( this . Series ) ;
451469 }
452470 catch ( MatchCountException e )
453471 {
@@ -828,5 +846,148 @@ private static IEnumerable<ISeries> GetSeries(ImmutableArray<IRecord> records, s
828846
829847 return seriesList ;
830848 }
849+
850+ /// <summary>
851+ /// Calculates statistical metrics for all series.
852+ /// </summary>
853+ private static ObservableCollection < SeriesMetrics > CalculateSeriesMetrics (
854+ IEnumerable < ISeries > series )
855+ {
856+ var metricsList = new ObservableCollection < SeriesMetrics > ( ) ;
857+
858+ foreach ( var s in series )
859+ {
860+ if ( s is LineSeries < DateTimePoint > lineSeries )
861+ {
862+ var points = lineSeries . Values . Cast < DateTimePoint > ( ) . ToList ( ) ;
863+
864+ if ( points . Any ( ) )
865+ {
866+ // Filter out null values to avoid skewing statistics
867+ var values = points
868+ . Where ( p => p . Value . HasValue )
869+ . Select ( p => p . Value . Value )
870+ . ToList ( ) ;
871+ var timestamps = points . Select ( p => p . DateTime ) . ToList ( ) ;
872+
873+ if ( values . Any ( ) )
874+ {
875+ var count = values . Count ;
876+ var min = values . Min ( ) ;
877+ var max = values . Max ( ) ;
878+ var mean = values . Average ( ) ;
879+
880+ // Calculate median
881+ var sortedValues = values . OrderBy ( v => v ) . ToList ( ) ;
882+ var mid = sortedValues . Count / 2 ;
883+ var median = ( sortedValues . Count % 2 == 0 )
884+ ? ( sortedValues [ mid - 1 ] + sortedValues [ mid ] ) / 2.0
885+ : sortedValues [ mid ] ;
886+
887+ var rangeStart = timestamps . Min ( ) ;
888+ var rangeEnd = timestamps . Max ( ) ;
889+
890+ var metrics = new SeriesMetrics (
891+ lineSeries . Name ?? "Unknown" ,
892+ count ,
893+ min ,
894+ max ,
895+ Math . Round ( mean , MetricsPrecision ) ,
896+ Math . Round ( median , MetricsPrecision ) ,
897+ rangeStart ,
898+ rangeEnd ) ;
899+
900+ metricsList . Add ( metrics ) ;
901+ }
902+ else
903+ {
904+ // All values were null
905+ var metrics = new SeriesMetrics (
906+ lineSeries . Name ?? "Unknown" ,
907+ 0 ,
908+ null ,
909+ null ,
910+ null ,
911+ null ,
912+ null ,
913+ null ) ;
914+
915+ metricsList . Add ( metrics ) ;
916+ }
917+ }
918+ else
919+ {
920+ // Empty series
921+ var metrics = new SeriesMetrics (
922+ lineSeries . Name ?? "Unknown" ,
923+ 0 ,
924+ null ,
925+ null ,
926+ null ,
927+ null ,
928+ null ,
929+ null ) ;
930+
931+ metricsList . Add ( metrics ) ;
932+ }
933+ }
934+ }
935+
936+ return metricsList ;
937+ }
938+
939+ /// <summary>
940+ /// Serializes the metrics data as tab-delimited text suitable for clipboard copying.
941+ /// </summary>
942+ public string SerializeMetrics ( )
943+ {
944+ if ( this . SeriesMetrics == null || ! this . SeriesMetrics . Any ( ) )
945+ {
946+ return string . Empty ;
947+ }
948+
949+ var lines = new List < string > ( ) ;
950+
951+ // Header row
952+ lines . Add ( "Series Name\t Count\t Min\t Max\t Mean\t Median\t Range Start\t Range End" ) ;
953+
954+ // Data rows
955+ foreach ( var metrics in this . SeriesMetrics )
956+ {
957+ var line = string . Join ( "\t " ,
958+ metrics . SeriesName ,
959+ metrics . Count . ToString ( ) ,
960+ metrics . MinFormatted ,
961+ metrics . MaxFormatted ,
962+ metrics . MeanFormatted ,
963+ metrics . MedianFormatted ,
964+ metrics . RangeStartFormatted ,
965+ metrics . RangeEndFormatted ) ;
966+
967+ lines . Add ( line ) ;
968+ }
969+
970+ return string . Join ( Environment . NewLine , lines ) ;
971+ }
972+
973+ /// <summary>
974+ /// Copies the metrics data to the clipboard.
975+ /// </summary>
976+ private void CopyMetricsToClipboard ( )
977+ {
978+ try
979+ {
980+ var serializedData = SerializeMetrics ( ) ;
981+ if ( ! string . IsNullOrEmpty ( serializedData ) )
982+ {
983+ Clipboard . SetData ( DataFormats . UnicodeText , serializedData ) ;
984+ }
985+ }
986+ catch ( Exception e )
987+ {
988+ Log . Default . Write ( LogSeverityType . Error , e , "Failed to copy metrics to clipboard." ) ;
989+ MessageBox . Show ( "Failed to copy metrics to clipboard." , "Copy Error" , MessageBoxButton . OK , MessageBoxImage . Error ) ;
990+ }
991+ }
831992 }
832993}
0 commit comments