I have implemented several xamdatacharts throughout my app. A new requirement to run my app on a tablet was given to me and basically the xamdatachart make this nearly impossible. I have 2 issues that have plagued me since I wrote the app in v11.1 then 12.2.
I have recently upgraded to 14.1 and have not seen any performance boost in the XamDataChart.
1) I have a chart using a LineSeries and a CategoryXAxis. The source is an observable collection of (DateTime, float). I attempt to simulate an EEG graph by updating that collection by index (so no FIFO). Collection is 4000 points for 8 seconds worth of data. I update the collection every 50ms so I am updating ~25 points of data plus 250 blank points to simulate a moving gap about 20 times a second. I have even used an custom implementation of an ObservableCollection that turns off notifications when I start the update and then sends a reset after all 275 points are updated vs just updating as they come in. The cpu was no different. Point is that this graph is the biggest cpu hog and on the tablet causes the UI to eventually become unresponsive. I am hoping that someone can tell me if there is a more efficient way to create an EEG simulated graph using any of the Infragistics controls?
2) I have another chart that can have N LineSeries but in this case they all use the CategoryDateTimeXAxis. Each series will have points coming in at no faster than 1/second. However, this graph show all the history. What I find is after several hours of running each point added takes longer and longer. I believe this is because the CategoryDateTimeXAxis sorts the data on every insert even though I know it is already sorted. Is there anything in the new version that I can do to optimize that xaxis type or a new way to use it to make this more efficient? I have had to limit each series to only hold 28800 (8 hours) points as beyond that the entire UI gets noticeably choppy each second. I have posted this question before about 18 months ago as a support ticket and the suggestion was to use a scatterline series instead but this had a similar jittery effect, slightly reduced, but happened all the time even when the series only had a few points in it (plus the implementation was much uglier).
Any new suggestions to optimize either of these scenarios?
Hi Valerie,
I just verified that the collection objects do not implement INotifyPropertyChanged so they cannot be firing off any events. Also, I am replacing the collection element, not updating a property of the object, so it shouldn't matter if they did implement that interface right?
I have actually been working on this chart performance for some time. I have implemented a few other chart controls to see if I can get better performance and so far only one has met my expectations (LightningChart). It uses about half the CPU to draw the same graph as Infragistics but as I have started using other Infragistics controls I don't want to include another 3rd party control into our product. I am still hoping we can find a more efficient way to create a sweeping EEG that doesn't kill a tablet.
Thanks,
Mike
Hello Mike,
I have been looking at your implementation and if you are updating your points at a specific index in the collection as opposed to adding new points to the collection are the individual points implementing property changed notifications and these are actually causing the chart to update?
Sincerely,
Valerie
Developer Support Supervisor - XAML
Infragistics
www.infragistics.com/support
Here is my implementation. Got some of this from the web so I can't really claim it.
I call SuspendNotifications before I start my update then NotifyChanges after. I could have used the AddItem method but in this case when the graph is not visible I do not call NotifyChanges to help with performance.
/*==============================================================================/ FileName : FastObservableCollection.cs// Project : Vision.Web// Compiler(s) : Visual Studio 2012// Copyright : © 2006-2014 Spectrum Medical Ltd// Description : This is a custom implementation of an ObservableCollection * that can be updated on a non UI thread and is supposed to be faster than the * default implementation of ObservableCollection by allowing a single collection * changed notification for a series of changes rather than one notification for * each individual change.//=============================================================================*/using System.Collections.Specialized;using System.Collections.ObjectModel;using System.Collections;using System;using System.Windows.Threading;using System.Collections.Generic;
namespace LiveOR.Classes.Collections{ /// <summary> /// This is a custom implementation of an ObservableCollection that can be updated on a non UI thread and is supposed to be faster than the /// default implementation of ObservableCollection by allowing a single collection changed notification for a series of changes rather than one notification for /// each individual change /// </summary> /// <typeparam name="T"></typeparam> /// <remarks reqid="0003" type="eng">The LiveOR Classes dll will provide a class to define a custom implementation of an ObservableCollection for handling collections that have many elements modified in groups</remarks> [Serializable] public class FastObservableCollection<T> : ObservableCollection<T> { /// <summary> /// This private variable holds the flag to /// turn on and off the collection changed notification. /// </summary> private bool suspendCollectionChangeNotification;
/// <summary> /// Initializes a new instance of the FastObservableCollection class. /// </summary> public FastObservableCollection() : base() { this.suspendCollectionChangeNotification = false; }
public FastObservableCollection(IEnumerable<T> list) : base(list) { }
/// <summary> /// This event is overriden CollectionChanged event of the observable collection. /// </summary> [field:NonSerialized] public override event NotifyCollectionChangedEventHandler CollectionChanged;
/// <summary> /// This method adds the given generic list of items as a range into current collection by casting them as type T. /// It then notifies once after all items are added. /// </summary> /// <param name="items">The source collection.</param> public void AddItems(IList items) { this.SuspendCollectionChangeNotification(); try { foreach (var i in items) { InsertItem(Count, (T)i); } } catch (Exception ex) { throw new InvalidCastException("Please check the type of item.", ex); } finally { this.NotifyChanges(); } }
/// <summary> /// Raises collection change event. /// </summary> public void NotifyChanges() { this.ResumeCollectionChangeNotification(); var arg = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset); this.OnCollectionChanged(arg); }
/// <summary> /// This method removes the given generic list of items as a range /// into current collection by casting them as type T. /// It then notifies once after all items are removed. /// </summary> /// <param name="items">The source collection.</param> public void RemoveItems(IList items) { this.SuspendCollectionChangeNotification(); try { foreach (var i in items) { Remove((T)i); } } catch (Exception ex) { throw new InvalidCastException( "Please check the type of items getting removed.", ex); } finally { this.NotifyChanges(); } }
/// <summary> /// Resumes collection changed notification. /// </summary> public void ResumeCollectionChangeNotification() { this.suspendCollectionChangeNotification = false; }
/// <summary> /// Suspends collection changed notification. /// </summary> public void SuspendCollectionChangeNotification() { this.suspendCollectionChangeNotification = true; }
/// <summary> /// This collection changed event performs thread safe event raising. /// </summary> /// <param name="e">The event argument.</param> protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e) { // Recommended is to avoid reentry // in collection changed event while collection // is getting changed on other thread. using (BlockReentrancy()) { if (!this.suspendCollectionChangeNotification) { NotifyCollectionChangedEventHandler eventHandler = this.CollectionChanged; if (eventHandler == null) { return; }
// Walk thru invocation list. Delegate[] delegates = eventHandler.GetInvocationList();
foreach (NotifyCollectionChangedEventHandler handler in delegates) { // If the subscriber is a DispatcherObject and different thread. DispatcherObject dispatcherObject = handler.Target as DispatcherObject;
try { if (dispatcherObject != null && !dispatcherObject.CheckAccess()) { // Invoke handler in the target dispatcher's thread... // asynchronously for better responsiveness. dispatcherObject.Dispatcher.BeginInvoke(DispatcherPriority.DataBind, handler, this, e); } else { // Execute handler as is. handler(this, e); } } catch (Exception) { // I keep getting null exceptions here for some reason. Throwing them on the floor to see if // the application still functions properly if I do } } } } } }}
Hi Mike,
Would it be possible to get your custom implementation of ObservableCollection? The more I think about it the more I think it actually should be making a difference. Are you sure that no PropertyChanged notifications are happening when you update the points?
Thanks Rob.
I kept using the CategoryDateTimeXAxis because I cannot guarantee all the points will be equally spaced. It just made my life a little easier if it took care of the spacing for me. I just wish I could tell it to not sort as an optional optimization.
I look forward to anything I can do to optimize the EEG chart as well. I have a C++ guy I work with that is giving me a lot of flack about .NET graphing performance... :)