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.
Could you define how the chart is not rendering correctly? Also, if you could provide a sample illustrating the problem that would help greatly.
-Graham
Hi Graham,
I also have the same question from you. Actually earlier i was using xamcharts and now I am planing to switch to xamdatachart. In xamchart theres a property RefreshEnable to control redrawing all points on the surface of chart on any updates. Lets suppose if your chart is binded with a live data collection. suppose you are getting updates in miliseconds instead of immidiately updating the graph you can put some mechanism to hold refreshenable say after 15 seconds wait and then redraw all points in xamchart. Now my question is in xamdatachart is there any such thing to hold your chart from being update immidiately on getting updates ?
Thanks.
The xamDataChart is designed to gracefully bind to live updating data with millisecond refresh rates, but depending your your requirements you may want to throttle the chart refreshes even so. Below is a sample that shows the experience of the xamDataChart when updating the data every 20 milliseconds. You can change the itemsource bindings in the Xaml to point at the property Throttled to see how things look if you throttle the chart refreshes to occur only every half second, while the data source continues to refresch every 20 milliseconds.
The xamDataChart doesnt attempt to internalize the notion of when it should be updating and when not, it just responds to the datasource events provided and tries to keep the chart visual updated in as efficient as possible a way.
The Xaml:
<Grid x:Name="LayoutRoot" Background="White"> <igChart:XamDataChart x:Name="theChart"> <igChart:XamDataChart.Axes> <igChart:CategoryXAxis x:Name="xAxis" ItemsSource="{Binding Data}" Label="{}{Label:hh:mm:ss}"/> <igChart:NumericYAxis x:Name="yAxis" /> </igChart:XamDataChart.Axes> <igChart:XamDataChart.Series> <igChart:LineSeries x:Name="line" XAxis="{Binding ElementName=xAxis}" YAxis="{Binding ElementName=yAxis}" ValueMemberPath="Value" ItemsSource="{Binding Data}" /> </igChart:XamDataChart.Series> </igChart:XamDataChart> </Grid>
And the code behind:
public partial class MainPage : UserControl, INotifyPropertyChanged { public MainPage() { InitializeComponent(); DataContext = this; Data = new ObservableCollection<TestDataItem>(); Throttled = new EventThrottlingCollection<TestDataItem>( Data, TimeSpan.FromSeconds(.5)); _timer.Interval = TimeSpan.FromMilliseconds( _millisecondRefreshInterval); _timer.Tick += Timer_Tick; _timer.Start(); } private int _millisecondRefreshInterval = 20; private Random _rand = new Random(); private double _currVal = 10.0; void Timer_Tick(object sender, EventArgs e) { if (_rand.NextDouble() > .5) { _currVal += _rand.NextDouble() * 1.0; } else { _currVal -= _rand.NextDouble() * 1.0; } DateTime now = DateTime.Now; Data.Add( new TestDataItem() { Label = now, Value = _currVal }); if (Data.Count > (TimeSpan.FromSeconds(30).TotalMilliseconds / _millisecondRefreshInterval)) { Data.RemoveAt(0); } } private DispatcherTimer _timer = new DispatcherTimer(); private ObservableCollection<TestDataItem> _data; public ObservableCollection<TestDataItem> Data { get { return _data; } set { _data = value; RaisePropertyChanged("Data"); } } private EventThrottlingCollection<TestDataItem> _dataView; public EventThrottlingCollection<TestDataItem> Throttled { get { return _dataView; } set { _dataView = value; RaisePropertyChanged("Throttled"); } } private void RaisePropertyChanged(string p) { if (PropertyChanged != null) { PropertyChanged( this, new PropertyChangedEventArgs(p)); } } public event PropertyChangedEventHandler PropertyChanged; } public class EventThrottlingCollection<T> : IEnumerable<T>, INotifyCollectionChanged { private IEnumerable<T> _inner; private DateTime _lastEvent; TimeSpan _batchInterval; public EventThrottlingCollection( IEnumerable<T> inner, TimeSpan batchInterval) { _inner = inner; INotifyCollectionChanged _coll = _inner as INotifyCollectionChanged; _lastEvent = DateTime.Now; _batchInterval = batchInterval; if (_coll != null) { _coll.CollectionChanged += Coll_CollectionChanged; } } void Coll_CollectionChanged( object sender, NotifyCollectionChangedEventArgs e) { if (DateTime.Now - _lastEvent > _batchInterval) { _lastEvent = DateTime.Now; 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; } }
Hope this helps! Let me know if you have any questions.
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
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.