Search code examples
.netmauisyncfusionsyncfusion-chart

SfCartesianChart with dynamic number of Series


I am trying to display a dynamic number of series in dotnet maui with SfCartesianChart (Version 22.2.12).

All the data from the chart are present in the ViewModel like:

public class ChartPageViewModel : BaseViewModel, IChartPageViewModel
{
    //Data are loaded asynchronously after the page load
    //after setting the data the PropertyChangedEvent is fired
    public ObservableCollection<IChartSeriesViewModel> DataSeries { get; set; }
}

public class ChartSeriesViewModel : IChartSeriesViewModel
{
    public string Title { get; set; }
    public IList<IChartPointViewModel> Data { get; set; }
}
    
public class ChartPointViewModel : IChartPointViewModel
{
    public DateTime Date { get; set; }
    public decimal Value { get; set; }
}

The data is bound with the series-Property using a Value-Converter:

<chart:SfCartesianChart Series="{Binding DataSeries, Converter={StaticResource DataSeriesConverter}}">
     <chart:SfCartesianChart.XAxes>
         <chart:CategoryAxis />
     </chart:SfCartesianChart.XAxes>
     <chart:SfCartesianChart.YAxes>
         <chart:NumericalAxis />
     </chart:SfCartesianChart.YAxes>
 </chart:SfCartesianChart>

the Value-Converter (IList to ChartSeriesCollection)

public class SfCartesianChartDataSeriesConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        if (value is IList<IChartSeriesViewModel> chartSeries)
        {
            ChartSeriesCollection series = new ChartSeriesCollection();

            foreach (var i in chartSeries)
            {
                series.Add(new FastLineSeries()
                {
                    ItemsSource = i.Data,
                    XBindingPath = nameof(IChartPointViewModel.Date),
                    YBindingPath = nameof(IChartPointViewModel.Value),
                });
            }

            return series;
        }

        return null;
    }

Unfortunately, the data is only shown/drawn when the view are redrawn (e.g. with a window resize).

The PropertyChangedEvent seems to work fine, because when I add a series in xaml the data is displayed immediately after the PropertyChangedEvent.

<chart:SfCartesianChart>
    <chart:SfCartesianChart.XAxes>
        <chart:CategoryAxis />
    </chart:SfCartesianChart.XAxes>
    <chart:SfCartesianChart.YAxes>
        <chart:NumericalAxis />
    </chart:SfCartesianChart.YAxes>

    <chart:FastLineSeries
           ItemsSource="{Binding DataSeries[0].Data}"
           XBindingPath="Date"
           YBindingPath="Value" />
</chart:SfCartesianChart>

But this is not the way to create a dynamic number of series.

How can the "redraw" be forced? Or how must the binding be made for this case?


Solution

  • Based on the answer from user22357370, I have found the following solution. Modified to keep the solution generic.

    Custom control, bases on SfCartesianChart:

    public class SfCartesianChartExt : SfCartesianChart
    {
        public static readonly BindableProperty SeriesCollectionProperty = BindableProperty.Create(
            nameof(SeriesCollection),
            typeof(ChartSeriesCollection),
            typeof(SfCartesianChartExt), null, BindingMode.Default, null, OnSeriesPropertyChanged);
    
        public ChartSeriesCollection SeriesCollection
        {
            get => (ChartSeriesCollection)GetValue(SeriesCollectionProperty);
            set => SetValue(SeriesCollectionProperty, value);
        }
    
        private static void OnSeriesPropertyChanged(BindableObject bindable, object oldValue, object newValue)
        {
            (bindable as SfCartesianChartExt)?.GenerateSeries(newValue);
        }
    
        private void GenerateSeries(object newValue)
        {
            if (newValue is ChartSeriesCollection collection)
            {
                ((INotifyCollectionChanged)newValue).CollectionChanged += DataPoint_CollectionChanged;
    
                Series.Clear();
    
                foreach (var item in collection)
    
                {
                    CreateSeries(item);
                }
            }
        }
    
        private void CreateSeries(ChartSeries item)
        {
            Series.Add(item);
        }
    
        private void DataPoint_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
        {
            switch (e.Action)
            {
                case NotifyCollectionChangedAction.Add:
                {
                    if (e.NewItems != null)
                    {
                        CreateSeries(e.NewItems[0] as ChartSeries);
                    }
                    break;
                }
                case NotifyCollectionChangedAction.Remove:
                {
                    Series.RemoveAt(e.OldStartingIndex);
                    break;
                }
            }
        }
    }
    

    Usage, with converter from question:

    <chart:SfCartesianChartExt Series="{Binding DataSeries, Converter={StaticResource DataSeriesConverter}}">
         <chart:SfCartesianChart.XAxes>
             <chart:CategoryAxis />
         </chart:SfCartesianChart.XAxes>
         <chart:SfCartesianChart.YAxes>
             <chart:NumericalAxis />
         </chart:SfCartesianChart.YAxes>
     </chart:SfCartesianChartExt>