@@ -48,6 +48,7 @@ public class GraphViewModel : INotifyPropertyChanged
4848 private IEnumerable < ISeries > _series ;
4949 private IEnumerable < ICartesianAxis > _xAxes ;
5050 private IEnumerable < ICartesianAxis > _yAxes ;
51+ private ObservableCollection < SeriesMetrics > _seriesMetrics ;
5152
5253 private string _xAxisLabel ;
5354 private string _yAxisLabel ;
@@ -76,6 +77,7 @@ public GraphViewModel(ImmutableArray<IRecord> records, string regularExpression,
7677 this . SourceFilePath = sourceFilePath ?? string . Empty ;
7778 this . TooltipWidth = 10 ;
7879 this . RegularExpression = regularExpression ?? string . Empty ;
80+ this . SeriesMetrics = new ObservableCollection < SeriesMetrics > ( ) ;
7981
8082 this . SampleData = records . Any ( )
8183 ? _records [ 0 ] . Content
@@ -331,8 +333,20 @@ public string Series4Axis
331333 }
332334 }
333335
336+ public ObservableCollection < SeriesMetrics > SeriesMetrics
337+ {
338+ get => _seriesMetrics ;
339+ set
340+ {
341+ _seriesMetrics = value ;
342+ RaisePropertyChanged ( nameof ( this . SeriesMetrics ) ) ;
343+ }
344+ }
345+
334346 public ICommand UpdateCommand => new UiBoundCommand ( ( ) => Update ( false ) ) ;
335347
348+ public ICommand CopyMetricsCommand => new UiBoundCommand ( ( ) => CopyMetricsToClipboard ( ) ) ;
349+
336350 private string GetDetectedData ( string regularExpression , string inputString )
337351 {
338352 var result = string . Empty ;
@@ -448,6 +462,9 @@ private void Update(bool isInitializing)
448462 string axisName = allNames . Any ( ) ? string . Join ( " / " , allNames ) : DefaultYAxisLabel ;
449463 this . YAxes = GetYAxes ( axisName ) ;
450464 }
465+
466+ // Calculate and update metrics
467+ this . SeriesMetrics = CalculateSeriesMetrics ( this . Series ) ;
451468 }
452469 catch ( MatchCountException e )
453470 {
@@ -828,5 +845,126 @@ private static IEnumerable<ISeries> GetSeries(ImmutableArray<IRecord> records, s
828845
829846 return seriesList ;
830847 }
848+
849+ /// <summary>
850+ /// Calculates statistical metrics for all series.
851+ /// </summary>
852+ private static ObservableCollection < SeriesMetrics > CalculateSeriesMetrics (
853+ IEnumerable < ISeries > series )
854+ {
855+ var metricsList = new ObservableCollection < SeriesMetrics > ( ) ;
856+
857+ foreach ( var s in series )
858+ {
859+ if ( s is LineSeries < DateTimePoint > lineSeries )
860+ {
861+ var points = lineSeries . Values . Cast < DateTimePoint > ( ) . ToList ( ) ;
862+
863+ if ( points . Any ( ) )
864+ {
865+ var values = points . Select ( p => p . Value ?? 0.0 ) . ToList ( ) ;
866+ var timestamps = points . Select ( p => p . DateTime ) . ToList ( ) ;
867+
868+ var count = values . Count ;
869+ var min = values . Min ( ) ;
870+ var max = values . Max ( ) ;
871+ var mean = values . Average ( ) ;
872+
873+ // Calculate median
874+ var sortedValues = values . OrderBy ( v => v ) . ToList ( ) ;
875+ var mid = sortedValues . Count / 2 ;
876+ var median = ( sortedValues . Count % 2 == 0 )
877+ ? ( sortedValues [ mid - 1 ] + sortedValues [ mid ] ) / 2.0
878+ : sortedValues [ mid ] ;
879+
880+ var rangeStart = timestamps . Min ( ) ;
881+ var rangeEnd = timestamps . Max ( ) ;
882+
883+ var metrics = new SeriesMetrics (
884+ lineSeries . Name ?? "Unknown" ,
885+ count ,
886+ min ,
887+ max ,
888+ Math . Round ( mean , 3 ) ,
889+ Math . Round ( median , 3 ) ,
890+ rangeStart ,
891+ rangeEnd ) ;
892+
893+ metricsList . Add ( metrics ) ;
894+ }
895+ else
896+ {
897+ // Empty series
898+ var metrics = new SeriesMetrics (
899+ lineSeries . Name ?? "Unknown" ,
900+ 0 ,
901+ null ,
902+ null ,
903+ null ,
904+ null ,
905+ null ,
906+ null ) ;
907+
908+ metricsList . Add ( metrics ) ;
909+ }
910+ }
911+ }
912+
913+ return metricsList ;
914+ }
915+
916+ /// <summary>
917+ /// Serializes the metrics data as tab-delimited text suitable for clipboard copying.
918+ /// </summary>
919+ public string SerializeMetrics ( )
920+ {
921+ if ( this . SeriesMetrics == null || ! this . SeriesMetrics . Any ( ) )
922+ {
923+ return string . Empty ;
924+ }
925+
926+ var lines = new List < string > ( ) ;
927+
928+ // Header row
929+ lines . Add ( "Series Name\t Count\t Min\t Max\t Mean\t Median\t Range Start\t Range End" ) ;
930+
931+ // Data rows
932+ foreach ( var metrics in this . SeriesMetrics )
933+ {
934+ var line = string . Join ( "\t " ,
935+ metrics . SeriesName ,
936+ metrics . Count . ToString ( ) ,
937+ metrics . MinFormatted ,
938+ metrics . MaxFormatted ,
939+ metrics . MeanFormatted ,
940+ metrics . MedianFormatted ,
941+ metrics . RangeStartFormatted ,
942+ metrics . RangeEndFormatted ) ;
943+
944+ lines . Add ( line ) ;
945+ }
946+
947+ return string . Join ( Environment . NewLine , lines ) ;
948+ }
949+
950+ /// <summary>
951+ /// Copies the metrics data to the clipboard.
952+ /// </summary>
953+ private void CopyMetricsToClipboard ( )
954+ {
955+ try
956+ {
957+ var serializedData = SerializeMetrics ( ) ;
958+ if ( ! string . IsNullOrEmpty ( serializedData ) )
959+ {
960+ Clipboard . SetData ( DataFormats . UnicodeText , serializedData ) ;
961+ }
962+ }
963+ catch ( Exception e )
964+ {
965+ Log . Default . Write ( LogSeverityType . Error , e , "Failed to copy metrics to clipboard." ) ;
966+ MessageBox . Show ( "Failed to copy metrics to clipboard." , "Copy Error" , MessageBoxButton . OK , MessageBoxImage . Error ) ;
967+ }
968+ }
831969 }
832970}
0 commit comments