Display aggregate values in chart legend?

Hello,

I would like to display some aggregate values for each series in a SfChart. I was able to style the legend to support the requirement easily enough, but am unclear on how to go about binding to an average, min and max of the Y-Axis values for each series. Could someone give me a push in the right direction? Here's a screenshot of what I'm working on; the yellow highlighted fields are currently just hard coded. Thanks very much for your help.
 
-Sean



8 Replies 1 reply marked as answer

SM Saravanan Madheswaran Syncfusion Team October 20, 2020 05:06 PM UTC

Hi Parker, 
 
Greetings from Syncfusion.  
 
We would like to let you know that we able to achieve your requirement by adding custom view using ItemTemplate of ChartLegend. We will share the sample for your requirement by tomorrow 21st October 2020.  
 
 
Regards, 
Saravanan.  



SM Saravanan Madheswaran Syncfusion Team October 21, 2020 02:04 PM UTC

Hi Parker,  
 
As we promised before, we have created sample to achieve your requirement using ChartLegend ItemTemplate support. Please find the sample from below location.  
 
 
Query: how to go about binding to an average, min and max of the Y-Axis values for each series. 
 
We can get corresponding series at ItemTemplate hence its binding was LegendItem. By extending the series we can get values at legend by below.  
 
public class CustomerFastLineSeries : FastLineSeries 
 { 
     public double MinValue { get; set; } 
     public double MaxValue { get; set; } 
     public double AvgValue { get; set; } 
 } 
 
Xaml: 
<chart:ChartLegend.ItemTemplate> 
    <DataTemplate> 
        <Grid Width="300" ColumnSpacing="5"> 
            . . .  
 
            <TextBlock . . Text="{Binding Series.MinValue}"/> 
            <TextBlock . . Text="{Binding Series.AvgValue}"/> 
            <TextBlock . . Text="{Binding Series.MaxValue}"/> 
        </Grid> 
    </DataTemplate> 
</chart:ChartLegend.ItemTemplate> 
 
Related UG: 
 
 
 
Regards, 
Saravanan. 



SP Sean Parker October 21, 2020 03:50 PM UTC

Hi Saravanan,

Thank you for this helpful example...very much appreciated. I should have clarified that in our use case, the chart is having data points added and potentially removed from each series once per second (it is a rolling time period), which means the Min/Max/Avg values in the legend need to change to reflect those values based on the current data.

It appears that the chart legend is only bound one time currently. Is there a way to rebind the aggregates in the legend on the same interval as the data?

Thanks for your help.

Sean


SP Sean Parker October 21, 2020 09:41 PM UTC

I thought it might be helpful to provide more specific detail on what I'm trying to accomplish.

I've created this IValueConverter which returns aggregations on the YAxis values of a Series.ItemsSource:

[Code]
public sealed class SeriesToYAxisAggregateConverter : IValueConverter
    {
        /// <param name="value"></param>
        /// <param name="targetType"></param>
        /// <param name="parameter">Determines what type of aggregate to return (Min, Max, Avg)</param>
        /// <param name="language"></param>
        /// <returns></returns>
        public object Convert(object value, Type targetType, object parameter, string language)
        {
            double aggregateValue = 0D;
            var series = (ChartSeriesBase) value;

            if (series != null)
            {
                var dataPoints = (ObservableQueue<DataPoint>) series.ItemsSource;
                if (dataPoints.Count > 0)
                {
                    switch (parameter?.ToString().ToLower())
                    {
                        case "min":
                            aggregateValue = dataPoints.Min(x => x.YValue);
                            break;
                        case "max":
                            aggregateValue = dataPoints.Max(x => x.YValue);
                            break;
                        case "avg":
                            aggregateValue = dataPoints.Average(x => x.YValue);
                            break;
                    }
                }
            }

            return aggregateValue.ToString("0.##");
        }

        public object ConvertBack(object value, Type targetType, object parameter, string language)
        {
            throw new Exception("Not Implemented");
        }
    }
[/Code]

...and I am using this converter in the ItemTemplate of my SfChart's ChartLegend property:

[Code]
<TextBlock Text="{Binding Series, Converter={StaticResource SeriesToYAxisAggregateConverter}, ConverterParameter=Min, Mode=OneWay}"
                                           Grid.Column="1"
                                           VerticalAlignment="Center"  
                                           HorizontalAlignment="Right" 
                                           Margin="5"
                                           FontSize="12"
                                           FontFamily="{StaticResource SegoeUI}"
                                           Foreground="{StaticResource WhiteColorBrush}"/>
                                <TextBlock Text="{Binding Series, Converter={StaticResource SeriesToYAxisAggregateConverter}, ConverterParameter=Avg, Mode=OneWay}"
                                           Grid.Column="2"
                                           VerticalAlignment="Center"  
                                           HorizontalAlignment="Right" 
                                           Margin="5"
                                           FontSize="12"
                                           FontFamily="{StaticResource SegoeUI}"
                                           Foreground="{StaticResource WhiteColorBrush}"/>
                                <TextBlock Text="{Binding Series, Converter={StaticResource SeriesToYAxisAggregateConverter}, ConverterParameter=Max, Mode=OneWay}"
                                           Grid.Column="3"
                                           VerticalAlignment="Center"  
                                           HorizontalAlignment="Right" 
                                           Margin="5"
                                           FontSize="12"
                                           FontFamily="{StaticResource SegoeUI}"
                                           Foreground="{StaticResource WhiteColorBrush}"/>
[/Code]

I've verified that the converter works as intended, but it is the binding is only evaluated one time for each series...what am I doing wrong?


SM Saravanan Madheswaran Syncfusion Team October 22, 2020 02:46 PM UTC

Hi Parker,   
   
Thanks for sharing the detailed use case.    
  
Since you have performed the series binding, the converter has been called only when the series gets changed instead of series, try bind with Series.ItemsSource. Or else please check the below workaround to calculate Min, Max, and Avg value at each time of ItemsSource PropertyChanged and collection changed. 
 
public class CustomerFastLineSeries : FastLineSeries 
{ 
    private ObservableCollection<ChartModel> dataPoints; 
 
    public CustomerFastLineSeries() : base() 
    { 
        //Register property change for itemsSource 
        RegisterPropertyChangedCallback(ChartSeriesBase.ItemsSourceProperty, CustomerFastLineSeries_PropertyChanged); 
    } 
 
    private void CustomerFastLineSeries_PropertyChanged(DependencyObject sender, DependencyProperty dp) 
    { 
        if (ItemsSource != null) 
        { 
            dataPoints = ItemsSource as ObservableCollection<ChartModel>; 
            //Ensure collection changed 
            dataPoints.CollectionChanged -= DataPoints_CollectionChanged; //Unhook when itemsSource changed 
            dataPoints.CollectionChanged += DataPoints_CollectionChanged; 
 
            CalculateCumulativeValue(); 
        } 
    } 
 
    private void CalculateCumulativeValue() 
    { 
        if (dataPoints.Count > 0) 
        { 
            MinValue = Math.Round(dataPoints.Min(x => x.YValue)); 
            MaxValue = Math.Round(dataPoints.Max(x => x.YValue)); 
            AvgValue = Math.Round(dataPoints.Average(x => x.YValue)); 
        } 
    } 
 
    private void DataPoints_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e) 
    { 
        CalculateCumulativeValue(); 
    } 
} 
 
Please check the modified sample from below link.  
 
 
 
 
Regards, 
Saravanan.  



SP Sean Parker November 2, 2020 09:56 PM UTC

Hey Saravanan,

In the case of our application it appears to be very challenging for us to extend the FastLineBitmapSeries class. I'm still trying to determine why that is the case so that we can implement the solution you provided, but in the interim I wanted to let you know that I did try binding to Series.ItemsSource in the legend as you suggested, but the legend bindings do not appear to reevaluate even when the data is changing. The data points on the chart which are also bound to the Series.ItemsSource do update on change. Is there some way to cause the legend to also update?

For reference, we are setting the Series.ItemsSource = an ObservableQueue<DataPoint>. DataPoint is a custom object that contains a date, YAxisValue among other things.

Thanks again for all your assistance.

-Sean



SP Sean Parker November 3, 2020 08:17 PM UTC

I've attached a modified version of your solution that uses my Converter and the ObservableQueue class in the hopes that it makes it easier to identify what I'm doing wrong. If you run the application, you can see that adding points Series.ItemsSource is reflected in the data points on the chart itself, but the legend is no longer reflecting changed data.

Thanks again for your help,

Sean

Attachment: ParkerSample_897cf187.zip


SJ Suyamburaja Jayakumar Syncfusion Team November 4, 2020 06:03 PM UTC

Hi Sean Parker, 
 
Thanks for your update.  
 
We would like to let you know that, while updating or removing the individual data point legend will not update, because it’s update based on series. Legend will be used to show particular series information. Instead of convertor please use inherited properties like below,  
 
XAML 
<TextBlock  . . .  Text="{Binding Series.MinValue}"/> 
<TextBlock . .  Text="{Binding Series.AvgValue}"/> 
<TextBlock . . . Text="{Binding Series.MaxValue}"/> 
 
C#[ CustomerFastLineSeries] 
private void CalculateCumulativeValue() 
        { 
            if (dataPoints.Count > 0) 
            { 
                MinValue = Math.Round(dataPoints.Min(x => x.YValue)); 
                MaxValue = Math.Round(dataPoints.Max(x => x.YValue)); 
                AvgValue = Math.Round(dataPoints.Average(x => x.YValue)); 
            } 
        } 
 
Please let us know if you need any further assistance. 
 
Regards, 
Suyamburaja J. 


Marked as answer
Loader.
Up arrow icon