Hi,
I'm Building a demo App with the XamDataChart, I have 32 LineSeries linked to observablecollections and refreshing data each 100 ms with 15 new DataPoints per Serie, with the databiding, the updates are so fast and the chart do not render properly, I read an XamChart post with a RefreshEnabled property, the XamDataChart have a property similar/equivalent to perform refresh of the chart manually.
Thank you in advance.
Yeah, if you are updating all of your collections from different threads there is one additional thing to watch out for, in that you might want all the series to update in one threaded interection so it does not appear that one series advances before the others. The below shows one way of achieving that using the reactive extensions approach, but there are, of course, many other ways of going about it:
public partial class MainPage : UserControl, INotifyPropertyChanged { private ObservableCollection<TestDataItem> _liveData = new ObservableCollection<TestDataItem>(); private ObservableCollection<TestDataItem> _liveData2 = new ObservableCollection<TestDataItem>(); public MainPage() { InitializeComponent(); DataContext = this; Data = new ManualResetProxy<TestDataItem>(_liveData); Data2 = new ManualResetProxy<TestDataItem>(_liveData2); SynchronizeRefreshes(); GenerateRandomBufferedData(_liveData, Data); GenerateRandomBufferedData(_liveData2, Data2); } private void SynchronizeRefreshes() { var needsRefresh = Observable.FromEvent <NeedsRefreshEventHandler, NeedsRefreshEventArgs>( (h) => new NeedsRefreshEventHandler(h), (h) => NeedsRefresh += h, (h) => NeedsRefresh -= h); var data1NeedsRefresh = needsRefresh.Where( (ev) => ev.EventArgs.Proxy == Data); var data2NeedsRefresh = needsRefresh.Where( (ev) => ev.EventArgs.Proxy == Data2); var refreshAllData = data1NeedsRefresh.Zip( data2NeedsRefresh, (l, r) => new List<ManualResetProxy<TestDataItem>>() { l.EventArgs.Proxy, r.EventArgs.Proxy }); refreshAllData.ObserveOnDispatcher().Subscribe( (items) => { foreach (var item in items) { item.Reset(); } }); } public void GenerateRandomBufferedData( IList<TestDataItem> target, ManualResetProxy<TestDataItem> proxy) { Random rand = new Random(); var liveValues = Observable.GenerateWithTime( 5.0, (v) => true, (v) => new TestDataItem { Label = DateTime.Now, Value = v }, (v) => TimeSpan.FromMilliseconds(20), (v) => { if (rand.NextDouble() > .5) { return v + rand.NextDouble(); } return v - rand.NextDouble(); }); var chunks = liveValues.BufferWithTime( TimeSpan.FromSeconds(.5)); chunks.ObserveOnDispatcher() .Subscribe( (items) => { foreach (var item in items) { target.Add(item); if (target[target.Count - 1].Label - target[0].Label > TimeSpan.FromSeconds(30)) { target.RemoveAt(0); } } if (NeedsRefresh != null) { NeedsRefresh( this, new NeedsRefreshEventArgs { Proxy = proxy }); } }); } private class NeedsRefreshEventArgs : EventArgs { public ManualResetProxy<TestDataItem> Proxy { get; set; } } private delegate void NeedsRefreshEventHandler(object sender, NeedsRefreshEventArgs args); private event NeedsRefreshEventHandler NeedsRefresh; private ManualResetProxy<TestDataItem> _data; public ManualResetProxy<TestDataItem> Data { get { return _data; } set { _data = value; RaisePropertyChanged("Data"); } } private ManualResetProxy<TestDataItem> _data2; public ManualResetProxy<TestDataItem> Data2 { get { return _data2; } set { _data2 = value; RaisePropertyChanged("Data2"); } } private void RaisePropertyChanged(string p) { if (PropertyChanged != null) { PropertyChanged( this, new PropertyChangedEventArgs(p)); } } public event PropertyChangedEventHandler PropertyChanged; }
Let me know how else I can help out.-Graham
All you are doing in these examples is to manage how many change notifications events are sent by your data sources, which may not even be necessary, depending on your situation, as the chart is designed to process many updates in rapid sucession. The ideas here should generalize to multiple series, without problems.
-Graham
By the way the chart does treat AddRange differently than an Add and will do less refreshes, but you would need to use a collection type that supported AddRange and raises the appropriate batch events. The other option presented here is to manually send the reset notifications when sending batches of values to the chart.
Here's an alternate version that uses the Reactive Extensions for .NET to generate/buffer the data and reduce the number of refreshes:
public partial class MainPage : UserControl, INotifyPropertyChanged { private ObservableCollection<TestDataItem> _liveData = new ObservableCollection<TestDataItem>(); public MainPage() { InitializeComponent(); DataContext = this; Data = new ManualResetProxy<TestDataItem>(_liveData); Random rand = new Random(); var liveValues = Observable.GenerateWithTime( 5.0, (v) => true, (v) => new TestDataItem { Label = DateTime.Now, Value = v }, (v) => TimeSpan.FromMilliseconds(20), (v) => { if (rand.NextDouble() > .5) { return v + rand.NextDouble(); } return v - rand.NextDouble(); }); var chunks = liveValues.BufferWithTime( TimeSpan.FromSeconds(.5)); chunks.ObserveOnDispatcher() .Subscribe( (items) => { foreach (var item in items) { _liveData.Add(item); if (_liveData[_liveData.Count - 1].Label - _liveData[0].Label > TimeSpan.FromSeconds(30)) { _liveData.RemoveAt(0); } } Data.Reset(); }); } private ManualResetProxy<TestDataItem> _data; public ManualResetProxy<TestDataItem> Data { get { return _data; } set { _data = value; RaisePropertyChanged("Data"); } } private void RaisePropertyChanged(string p) { if (PropertyChanged != null) { PropertyChanged( this, new PropertyChangedEventArgs(p)); } } public event PropertyChangedEventHandler PropertyChanged; } public class ManualResetProxy<T> : IEnumerable<T>, INotifyCollectionChanged { private IEnumerable<T> _inner; private DateTime _lastEvent; public ManualResetProxy( IEnumerable<T> inner) { _inner = inner; INotifyCollectionChanged _coll = _inner as INotifyCollectionChanged; } public void Reset() { if (CollectionChanged != null) { CollectionChanged( this, new NotifyCollectionChangedEventArgs( NotifyCollectionChangedAction.Reset)); } } public IEnumerator<T> GetEnumerator() { return _inner.GetEnumerator(); } IEnumerator IEnumerable.GetEnumerator() { return _inner.GetEnumerator(); } public event NotifyCollectionChangedEventHandler CollectionChanged; } public class TestDataItem { public DateTime Label { get; set; } public double Value { get; set; } }
Cool, huh?-Graham
HI Graham,
Thanks for your reply. Well it seems fine for one series what if I have to manage multiple series. Actually in my case I have to add remove and update multiple series in the chart. And by your example I belive its fine for one series but not sure for multiple and run time add and remove series senario. Let me try your proposed solution and ill let you know the result.
Thanks.