SfChart cyclic update performance

Hello,

In our application we are using a SfChart to plot data coming from a machine (temperature, force, position etc.). Currently we have up to 20 data series (FastLineBitmapSeries) which are updated every 100 ms. We now face the problem that we can't use more than 10 data series for more than 5 minutes or the application will start to freeze. We then created a quick test app which only uses a SfChart and saw that with 20 data series and 100ms cycle, the chart starts to freeze after about 6 minutes (having plotted about 72'000 data points by then).

EDIT: We also identified a problem when wanting to clear the graph. When just clearing the ObservableCollections it will slow down the application due to an ever increasing execute time of SfChart.UpdateArea method. When instantiating new ObservableCollections we could not observe that behaviour.

Sidenote: We know that using a StrokeThickness of 1 would further improve performance but for our application, which has a dark theme, we found that we need to have a StrokeThickness of 2 in order to improve readability.

Is there any other way to improve the performance of the chart for cylic updates?

Version: 16.4.0.42

Thanks in advance

Kind regards, 

Michael


Attachment: ChartTest_2818d9f7.7z

18 Replies

EM Emanuel February 27, 2021 11:44 AM UTC

Hello,
I am also facing the problem of slow UpdateArea when clearing the observable collection. However I am also facing this issue when resetting the collection as well.
Did you manage to find any solution for this?

Thnaks, Emanuel


MS Michael Steiner March 1, 2021 12:03 PM UTC

Hi Emanuel,

We weren't really able to eliminate the peformance issue for long runs. But one thing we noticed is that we had a memory leak with series that we did not use in our viewmodels anymore (called clear on the collection) but were still kept alive by the SfChart by some references. We solved this issue by calling Dispose on the SfChart whenever the view was disposed. This helped to solve the problem with slowing app over time.
Hope this helps you as well.


YP Yuvaraj Palanisamy Syncfusion Team March 1, 2021 01:55 PM UTC

Hi Emanuel, 
  
We would like to suggest that to improve the performance by setting of ItemsSource to be null and then resetting the ItemsSource property. Also, please refer to the below steps to optimize the chart performance for live data for your reference,
 
      
 
·       Use FastLineBitmapSeries instead of if you are using FastLineSeries. 
·       Set the StrokeWidth of FastLineBitmapSeries to “1” by default value of it “2”. 
·       Set the EnableAntiAliasing property as false for FastLineBitmapSeries.   
·       Remove LabelFormat for Axis if any. 
·       Should not use ChartSeries ListenPropertyChange 
·       Set Minimum and Maximum as fixed for axis. 
 
For more details, please refer the below link. 
 
@ Michael Steiner – Thanks for your update. 
 
Regards, 
Yuvaraj 



EM Emanuel March 5, 2021 10:51 PM UTC

Hello,

Thanks for the input. I also noticed the memory leak and I solved it by caching the views and the viewModels and disposing them accordingly as well as setting the intemSource to null.
The problem however still remains when the observable collection that holds the points is updated. Here is a screenshot of the profiler. It looks like the main culprit is Layout axis.

Thanks,
Emanuel



YP Yuvaraj Palanisamy Syncfusion Team March 8, 2021 12:50 PM UTC

Hi Emanuel, 
 
When we are setting ItemsSource to be null, the axis has been updated to it’s default range (0 to 1). Hence, the SfChartLayoutAxis has been updated with default range value. By setting of ChartAxis minimum and maximum property for fixed range it does not updated. 
 
Note: If you are using DispatcherTimer for real time chart update, please update chart at background thread.Due to DispatcherTimer will run on main thread. 
 
Regards, 
Yuvaraj. 



EM Emanuel March 10, 2021 08:15 PM UTC

Hello, thanks for the input.
I am actually using the min and max limits on my axes, the problem is that the y axis limits have to be updated with the data.
My use case is a bit unconventional. Basically I have bound around 20 series in one graph and are grouped in rows of 3 series each
The number of series is dynamic and can change at any time and I need to have this change reflected dynamically. 
At the same time, the actual points can also change at any time. The source of the change can be a separate process but also some sliders in the same UI window. Therefore I don't see how I can do the updates on the background thread since the Observable collections that are bound to the graph are being updated.
Can you elaborate more about this part, maybe there is something I'm missing.

Thanks,
Emanuel


YP Yuvaraj Palanisamy Syncfusion Team March 11, 2021 05:39 PM UTC

Hi Emanuel, 
 
When we update the chart datapoints in the background thread, the performance has been improved instead of if we are using DispatcherTime. Which is used for a synchronous operation into an asynchronous one is to run it on a separate thread with the help of Task.Run, please find the code example below. 
 
public async void GenerateData() 
{ 
    await Task.Run(() => 
    { 
        LoadData(); 
    });             
} 
 
Also, I have attached the sample for your reference, please find the sample from the below link. 
 
  
Regards, 
Yuvaraj.


EM Emanuel March 12, 2021 10:29 AM UTC

Hello,

Well, yes I am updating the data on the background thread as well. More than that, I am using a fast observable collection that notifies only once when a range of points are changed.
The problem is the control itself. When I modify a series that has a few thousand points  the update Area is triggered (I don't know why to be honest, since the width and height of my chart are fixed anyway) and this operation is very slow. This part cannot be offloaded to the background.

Is there a way to control this updateArea call?

Also, regarding your point about setting the ItemSource to null that triggers the axis recalculation, in my case, I am just clearing the collection and replacing the contents (it is not set to null). Could it be that if the item source is just cleared the same effect is triggered?

Thanks,
Emanuel


SM Saravanan Madheswaran Syncfusion Team March 15, 2021 12:24 PM UTC

Hi Emanuel, 
 
Thanks for the details, we would like to suggest checking by adding items source after suspend the chart update like below code snippet, mean time we validate further and update you the solution or workaround in two business days (17th March 2021). 
 
StackingColumnChart1.SuspendSeriesNotification(); 
 
/// Load items source 
 
StackingColumnChart1.ResumeSeriesNotification(); 
 
 
Regards, 
Saravanan. 



EM Emanuel March 15, 2021 04:22 PM UTC

Hello,

Indeed, I am using the layout suspend notification when I change the series and it works fine.
The problem appears only when the Series collection is updated. I did a bit of search and Indeed the SfChart.LayoutAxis(Size) is the culprit:
  private void LayoutAxis(Size availableSize)
    {
      if (this.ChartAxisLayoutPanel != null)
      {
        this.ChartAxisLayoutPanel.UpdateElements();
        this.ChartAxisLayoutPanel.Measure(availableSize);
        this.ChartAxisLayoutPanel.Arrange(availableSize);
      }
The measure and Arrange methods take a lot of time even when is nothing to recompute(no layout change occurs). In order to avoid all those costly updates I had to do a very ugly hack. In my derived class i used reflection to set the UpdateAction flags:
    public class SfChartExt : SfChart
    {
        private readonly PropertyInfo _updateActionProperty;
        public SfChartExt()
        {
            _updateActionProperty = typeof(SfChart).GetProperty("UpdateAction", BindingFlags.Instance | BindingFlags.NonPublic);
        }

        public void SetUpdateAction()
        {
            _updateActionProperty?.SetValue(this, 16|2|4, null);
        }
}

And the metod is called when the collection update is triggered in my implementation of Observable collection:
        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 (_suspendCollectionChangeNotification) return;
                var eventHandler = CollectionChanged;
                if (eventHandler == null) {
                    return;
                }

                // Walk thru invocation list.
                var delegates = eventHandler.GetInvocationList();

                foreach (var @delegate in delegates) {
                    var handler = (NotifyCollectionChangedEventHandler)@delegate;
                    // If the subscriber is a DispatcherObject and different thread.

                    if (handler.Target is DispatcherObject dispatcherObject
                        && !dispatcherObject.CheckAccess()) {
                        // Invoke handler in the target dispatcher's thread...
                        // asynchronously for better responsiveness.
                        dispatcherObject.Dispatcher.BeginInvoke(DispatcherPriority.DataBind, new Action(() =>
                        {
                            var myField = typeof(Syncfusion.UI.Xaml.Charts.ChartSeriesBase).GetProperty("ActualArea", BindingFlags.Instance | BindingFlags.NonPublic);
                            if(myField?.GetValue(handler.Target) is SfChartExt chart)  chart.SetUpdateAction();
                    
                            handler(this, e);
                        }));
                    } else {
                        // Execute handler as is.
                        handler(this, e);
                    }
                }
            }
        }

Now everything is very fast since there is no layout pass done. Is there a clean way to skip the layout phase without this hack?

Thanks,
Emanuel


YP Yuvaraj Palanisamy Syncfusion Team March 16, 2021 01:21 PM UTC

Hi Emanuel, 
 
Currently we are validating your code example and we will try resolve the possible way of solution in our source level. And we will update you with complete details on or before 19th March 2021. 
 
Regards, 
Yuvaraj. 



SM Saravanan Madheswaran Syncfusion Team March 22, 2021 12:28 PM UTC

Hi Emanuel, 
 
Sorry for the delay, we have forwarded the details to our development team and they are working on it. We will share the further details on or before 24th March 2021. We appreciate your patience until then.  
 
Regards, 
Saravanan.  



SM Saravanan Madheswaran Syncfusion Team March 23, 2021 06:05 PM UTC

Hi Emanuel,  
 
Thanks for your patience,  
 
After validated with our development team, stated ignoring axis layout update cause issue, if any changes with range or layout size change or grid line tick position update. So, we consider it as feature request and find the below feedback link to track the status of the feature. 
 
 
Please cast your vote to make it count. We will prioritize the features every release based on the demands and we do not have an immediate plan to implement this feature since we committed with already planned work. So, this feature will be available in any of our upcoming releases.     
 
Regards, 
Saravanan.  



DA Daniel March 31, 2021 10:33 AM UTC

Hello,

I would like to thank Emanuel for finding the workaround, as his solution finally after two years fixed the problem with Y axis flicker on fast updates.
(the thread in question is https://www.syncfusion.com/forums/146691/sfchart-axis-flicker)
After the former incident closed, the bug was indeed fixed, but only to an extent. 
We have found that in our CPU heavy application, the Y axis flickers when zoomed in around 60%.
As stated before, LayoutAxis was the culprit.
However, Emanuel's solution breaks zooming, as it needs to invalidate the chart area afterwards.
I have edited his solution like this:

public class SfChart : Syncfusion.UI.Xaml.Charts.SfChart
    {
        [Flags]
        enum UpdateActionType
        {
            Nothing = 0,
            Create = 0x2,
            UpdateRange = 0x4,
            Layout = 0x8,
            Render = 0x10,
            LayoutAndRender = Layout | Render,
            UpdateRangeAndArrange = Render | Layout | UpdateRange,
            Invalidate = Render | Layout | UpdateRange | Create
        }

        private bool _lastZoom = false;
        private int _cooldown = 0;

        public SfChart() : base()
        {
            this.ZoomChanging += SfChart_ZoomChanging;
            this.ZoomChanged += SfChart_ZoomChanged;
            this.SelectionZoomingStart += SfChart_SelectionZoomingStart;
            this.SelectionZoomingEnd += SfChart_SelectionZoomingEnd;
        }

        private void SfChart_SelectionZoomingEnd(object sender, Syncfusion.UI.Xaml.Charts.SelectionZoomingEndEventArgs e)
        {
            _lastZoom = true;
        }

        private void SfChart_ZoomChanged(object sender, Syncfusion.UI.Xaml.Charts.ZoomChangedEventArgs e)
        {
            _lastZoom = true;
        }

        private void SfChart_SelectionZoomingStart(object sender, Syncfusion.UI.Xaml.Charts.SelectionZoomingStartEventArgs e)
        {
            _lastZoom = true;
        }

        private void SfChart_ZoomChanging(object sender, Syncfusion.UI.Xaml.Charts.ZoomChangingEventArgs e)
        {
            _lastZoom = true;
        }

        public void SetUpdateAction()
        {
            if (_cooldown >= 2)
            {
                _cooldown = 0;
                _lastZoom = false;
            }    

            if (!_lastZoom)
            {
                int flag = (int)(UpdateActionType.Create | UpdateActionType.UpdateRange | UpdateActionType.Render);
                this.SetPropertyValue("UpdateAction", flag);
            }
            else
            {
                _cooldown++;
            }
        }
    }

This code seems to work 99% of time. However, due to concurrency issues, this code does not guarantee that the zooming will work after the data reloads.
I am unable to fix this 100% without source code changes.
Therefore, I really think the layout axis update should be treated as a bug, as it causes rare visual artifacts on the Y axis.
I would like to vote for the feature, but the link does not open for me.

Again, thank everyone that worked on this solution.


SM Saravanan Madheswaran Syncfusion Team April 1, 2021 03:21 PM UTC

Hi Daniel, 
 
Thanks for your update.  
 
The feature link was public, please login with your register email id to vote on the feature. For considering the bug we have forward this to our development team, we will update the status in 2 business days (7th April 2021). 
 
Thank you, 
Saravanan. 



SM Saravanan Madheswaran Syncfusion Team April 8, 2021 11:55 AM UTC

Hi Daniel & Emanuel, 
 
Thanks for your patience, 
 
As per the development team, the modifications to the axis layout would affect many features of the chart. So, currently we are not able to consider it as a bug, and since we have committed to previously scheduled work, the timeline will be uncertain. 
 
Regards, 
Saravanan.  



EM Emanuel April 29, 2021 02:09 PM UTC

Hello,

Thanks Daniel for the update, indeed i did not consider zoom or other impacts.

@Saravanan Madheswaran
Thank you for raising the performance enhancement with the dev team.

Best regards,
Emanuel


SM Saravanan Madheswaran Syncfusion Team April 30, 2021 09:06 AM UTC

Hi Emanual, 
 
Most welcome, As promised the feature will be available in any of our upcoming release, please follow the feedback link 
 
Regards, 
Saravanan. 


Loader.
Up arrow icon