Chart GetStream with MVVM.

Hi,

I wish to get the chart image stream with the MVVM pattern. I don't have direct access to the chart control.
var stream = await chart.GetStreamAsync(); //This won't work for me as I have no chart reference.

I have looked at all the events and there is nothing I can hook into and get a chart reference. As a test I
converted the LostFocus
event to a command. This worked in that I was to get a chart reference. Obviously
this cannot work in in practice as the user has to
cause the lost focus event which I cannot rely on.

Is there some way I can get this working?

Thanks

Mark.



12 Replies

DD Devakumar Dhanapoosanam Syncfusion Team June 25, 2020 01:44 AM UTC

Hi Mark, 
 
Greetings from Syncfusion. 
 
Currently we are working on your requirement and we will update you the details on or before June 26, 2020. 
 
Regards, 
Devakumar D 



DD Devakumar Dhanapoosanam Syncfusion Team June 28, 2020 05:47 PM UTC

Hi Mark, 
 
Still we need some more time to find the feasible solution for your requirement and we will update you further details on or before June 30, 2020. Sorry for the delay. 
 
Regards, 
Devakumar D 



MA Mark June 29, 2020 05:33 AM UTC

Thanks for the update....


DD Devakumar Dhanapoosanam Syncfusion Team June 30, 2020 02:03 PM UTC

Hi Mark, 
 
Thanks for your patience. 
 
Solution 1: 
 
We can achieve this requirement by declaring Func in the ViewModel and can bind the GetStreamAsync method of chart to the field as demonstrated in the below code snippet. 
 
XAML: 
<chart:SfChart x:Name="chart" HorizontalOptions="FillAndExpand" VerticalOptions="FillAndExpand"> 
        …. 
</chart:SfChart> 
<Button Text="getStream" Clicked="Button_Clicked"/> 
 
 
C#: [ViewModel.cs] 
public class ViewModel 
{ 
        public Func<object> GetStream; 
         
} 
 
C#: 
public partial class MainPage : ContentPage 
{ 
        ViewModel viewModel; 
        public MainPage () 
        { 
            InitializeComponent (); 
 
            viewModel = new ViewModel(); 
            this.BindingContext = viewModel; 
 
            viewModel.GetStream = () => chart.GetStreamAsync(); 
        } 
 
        private void Button_Clicked(object sender, EventArgs e) 
        { 
          //get the chart stream 
             var stream = viewModel.GetStream.Invoke(); 
        } 
} 
 
Solution 2: 
 
Also, we would like to let you know that proper event to achieve your requirement is SeriesRendered which invoke once series has been rendered in view. Itself you can get the chart reference by your pattern is mainly on MVVM. Hence prepared the sample with EventToCommandBehavior for SeriesRendered event as per in below code snippet, 
 
XAML: 
<chart:SfChart x:Name="chart" HorizontalOptions="FillAndExpand" VerticalOptions="FillAndExpand"> 
      <chart:SfChart.Behaviors> 
              <local:EventToCommandBehavior EventName="SeriesRendered" Command="{Binding  
                           ChartSeriesRenderedCommand}" CommandParameter="{x:Reference chart}"/> 
        </chart:SfChart.Behaviors> 
</chart:SfChart> 
 
C#: [ViewModel.cs] 
public ICommand ChartSeriesRenderedCommand { get; private set; } 
 
public ViewModel() 
{ 
       …. 
       ChartSeriesRenderedCommand = new Command<SfChart>(ChartRenderedEvent); 
} 
 
private void ChartRenderedEvent(object args) 
{ 
       //here you can get the chart reference 
       var chart = args as SfChart; 
 
       var stream = chart.GetStreamAsync(); 
} 
 
 
 
Please refer the below KB for more details, 
 
 
Please let us know if you need any further assistance on this. 
 
Regards, 
Devakumar D 



MA Mark July 1, 2020 09:51 AM UTC

Hi,

Thanks for this but but did you actually test it? You provided a non MVVM solution which I cannot use and your ChartRenderedEvent stream is null!

The code should be: var stream = await chart.GetStreamAsync();

I also added 3 series to test how often the "SeriesRendered" event is fired. I thought that it would be fired 3 times but I see that it is fired once. A better name would be "ChartRendered".

Thank you for your help in resolving this.

Mark.






MA Mark July 1, 2020 11:53 AM UTC

Hi,

Just found a problem using SeriesRendered. If I update the chart it doesn't get triggered. The underlying data is updated and the chart reflects this but I haven't added/deleted any series and so doesn't get fired?

Is there a workaround for this?

Thanks

Mark.


DD Devakumar Dhanapoosanam Syncfusion Team July 2, 2020 06:10 PM UTC

Hi Mark, 
 
Sorry for the inconvenience caused. 
 
We have achieved your requirement by using the Func<Task<Stream>> GetStream in the ViewModel whenever the GetStream Func invoked it will trigger the chart GetStreamAsync. Please refer the below code snippet for more details, 
 
C#: 
public partial class MainPage : ContentPage 
{ 
        ViewModel viewModel; 
        public MainPage () 
        { 
             InitializeComponent (); 
 
             viewModel = new ViewModel(); 
             this.BindingContext = viewModel; 
        } 
 
        private async void Button_Clicked(object sender, EventArgs e) 
        { 
            //get the chart stream 
            if(viewModel.GetStream != null) 
            { 
                var stream = await viewModel.GetStream.Invoke(); 
            } 
        } 
} 
 
public class ViewModel : INotifyPropertyChanged 
{ 
        …. 
        public Func<Task<Stream>> GetStream; 
 
        public ViewModel() 
        { 
            …. 
            ChartSeriesRenderedCommand = new Command<SfChart>(ChartRenderedEvent); 
        } 
 
        private void ChartRenderedEvent(object args) 
        { 
            //here you can get the chart reference 
            var chart = args as SfChart; 
            GetStream = async () => await chart.GetStreamAsync(); 
        } 
        …. 
} 
 
Please find the modified sample from the below link, 
 
 
If the above suggested solution does not satisfy your requirement can you please explain more details of your requirement using the chart GetStreamAsync use case? 
 
Please let us know if you need any further assistance. 
 
Regards, 
Devakumar D 



MA Mark July 3, 2020 08:35 AM UTC

Hi,

Thank you for the update. I looked at your solution I cant see it working for me(sorry if I am misreading your code).

I have included my SF sample app. Please follow these steps:

1. Load the solution and add a break point in the ChartRenderedCommand in ChartViewModel.
2. Run the Android version (it actually doesn't matter). I have it running in the Genymotion free emulator.
3. Press the Start button.
4. The break point is hit and chartStream.Length is > 0 and so the chart data has been captured. All good.
5. Continue to run.
6. Go to the Chart tab and confirm the chart is drawn with 3 series.

7. Press the Add Data button and the chart is updated. Notice that the break point is not hit.

Please modify my code so that the break point is hit every time the data is updated. The fully updated (Nugets) sample project is attached.

Thanks

Mark.





Attachment: FreshMVVMSFDataGrid_38252c34.zip


DD Devakumar Dhanapoosanam Syncfusion Team July 6, 2020 11:45 AM UTC

 
Thanks for your update. 
 
We have checked the provided sample and we would like to let you know that the chart SeriesRendered event triggered once series has been rendered in view or while update the series ItemsSource dynamically. 
 
As we said earlier your requirement can be achieved by using the Func<Task<Stream>> GetStream in the ViewModel and call this in the chart series underlying data Vehicles CollectionChanged Event as per in the below code snippet which gets called every time when the data is updated as per your requirement,  
 
C#: [ChartViewModel.cs] 
 
public ChartViewModel() 
{ 
      Vehicles.CollectionChanged += Vehicles_CollectionChanged;    
} 
 
bool isChartRendered; 
 
public Func<Task<Stream>> GetStream; 
 
private async void Vehicles_CollectionChanged(object sender,  
                                       System.Collections.Specialized.NotifyCollectionChangedEventArgs e) 
{ 
      if (e.Action == System.Collections.Specialized.NotifyCollectionChangedAction.Add) 
      { 
           if (isChartRendered) 
           { 
                    if (GetStream != null) 
                    { 
                        var stream = await GetStream.Invoke(); 
                    } 
           } 
      } 
} 
 
public ICommand ChartRenderedCommand 
{ 
     get 
     { 
                return new Command(async (eventArgs) => 
                { 
                    if (eventArgs != null) 
                    { 
                        SfChart chart = eventArgs as SfChart; 
                        if (chart != null) 
                        {  
                            GetStream = async () => await chart.GetStreamAsync();  
                            Stream chartStream = await GetStream.Invoke(); 
                            isChartRendered = true; 
                        } 
                    } 
                }); 
     } 
} 
 
We have modified the provided sample based on your requirement and you can download the sample from below link, 
 
 
Please let us know if you need any further assistance on this. 
 
Regards, 
Devakumar D 



MA Mark July 8, 2020 10:01 AM UTC

Hi,

Thank you for your feedback. Unfortunately your solution is not practical as

Vehicles.CollectionChanged += Vehicles_CollectionChanged;  

gets hit for every change. I have modified our sample so that 10 vehicles get added on the Add button press. The above event get fired 10 times.

In my app I am adding 50 to 100 data points each time and this will cause a big performance impact. To be honest this approach is a hack and it looks like the chart (possibly your most popular control) does not support MVVM binding for getting the chart stream.

All serious Xamarin Forms apps are written using the MVVM pattern. Can you please suggest a better way or log a change request to add this feature.

I have added the updated project.

Thanks

Mark.



Attachment: FreshMVVMSFDataGrid_5acf3459.zip


MA Mark July 8, 2020 11:54 AM UTC

Hi,

I solved it. I use SeriesRendered  to get chart instance. I then use a message triggered elsewhere to call the func.

My command:

#region Property ChartRenderedCommand

        public Func<Task<Stream>> GetChartStreamFunc;

        public ICommand ChartRenderedCommand
        {
            get
            {
                return new Command(async (eventArgs) =>
                {
                    if (eventArgs != null)
                    {
                        SfChart chart = eventArgs as SfChart;
                        if (chart != null)
                        {
                            GetChartStreamFunc = async () => await chart.GetStreamAsync();
                        }
                    }
                });
            }
        }

        #endregion

and the function that is called by a message:

        public async void CreateReportData(ResultsDescriptionValues resultsDescriptionValues)
        {
            ResultsDescription.ResultsDescription resultDescription = new ResultsDescription.ResultsDescription(WizardFlowType, Illustration);
            ResultsDescription = resultDescription.CreateResultsDescription(resultsDescriptionValues);
            ChartImage = await GetChartStreamFunc.Invoke();
        }

your "Func<Task<Stream>> GetChartStreamFunc;" was the important part.

Thanks

Mark.



SJ Suyamburaja Jayakumar Syncfusion Team July 9, 2020 06:19 AM UTC

Hi Mark,  
  
Thanks for your suggestion. we are glad to hear that your requirement has been achieved.  
  
Please let us know if you need any further assistance on this.  
  
Regards,  
Suyamburaja J. 


Loader.
Up arrow icon