iOS Charting Documentation - SciChart
Tutorial 06 - Adding Realtime Update

So far all the tutorials in this series have focused on static charts where the data doesn't change. Assuming you have completed  Tutorial 05 - Working with Tooltips and Legends, we will now make some changes to update the data dynamically.

Tutorials Repository

As it was mentioned previously - we've created Git repository with all Tutorials. So you can clone/download the appropriate project you need!

SciChart has the concept of RenderableSeries and DataSeries. RenderableSeries present the data, while DataSeries hold the X,Y data and manage updates.

You can do realtime updates in three ways:
- update existing data;
- append new data with setting FIFO capacity;
- append new data with holding old data

Updating Data Values

Our CreateDataSeries method currently looks like this:

CreateDataSeries()
Copy Code
        void CreateDataSeries()
        {
            // Init line data series
            _lineDataSeries = new XyDataSeries<Double, Double>();
            // Naming data series, so its name will be shown in LegendModifier
            _lineDataSeries.SeriesName = "LineSeries";
            for (var i = 0; i < 500; i++)
            {
                _lineDataSeries.Append(i, Math.Sin(i * 0.1));
            }
            // Init scatter data series
            _scatterDataSeries = new XyDataSeries<Double, Double>();
            // Naming data series, so its name will be shown in LegendModifier
            _scatterDataSeries.SeriesName = "ScatterSeries";
            for (var i = 0; i < 500; i++)
            {
                _scatterDataSeries.Append(i, Math.Cos(i * 0.1));
            }
        }

This creates some DataSeries and appends some static data.

Now let's animate it.

Firstly, we need to add two instance variables - timer and phase:

New variables
Copy Code
    public partial class ViewController : UIViewController
    {
        private SCIChartSurface _surface;
        private XyDataSeries<Double, Double> _lineDataSeries;
        private XyDataSeries<Double, Double> _scatterDataSeries;

        private SCIFastLineRenderableSeries _lineRenderableSeries;
        private SCIXyScatterRenderableSeries _scatterRenderableSeries;
        // timer, used for updating data
        private NSTimer _timer;
        // phase variable used for data slipping
        private double _phase = 0.0;

 

We added an NSTimer property and will initialize it in the overriden ViewWillAppear method. Also we will update the CreateDataSeries() method and call DataSeries.Update method to change its data

ViewWillAppear() and ViewWillDisappear()
Copy Code
        public override void ViewWillAppear(bool animated)
        {
            base.ViewWillAppear(animated);
            if(_timer == null)
            {
                _timer = NSTimer.CreateRepeatingScheduledTimer(0.01, (timer) => 
                {
                    for(var i=0; i<500; i++)
                    {
                        _lineDataSeries.UpdateYAt(i, Math.Sin(i * 0.1 + _phase));
                        _scatterDataSeries.UpdateYAt(i, Math.Cos(i * 0.1 + _phase));
                    }
                    _phase += 0.01;
                    _surface.InvalidateElement();
                });
            }
        }
       public override void ViewWillDisappear(bool animated)
        {
            base.ViewWillDisappear(animated);
            _timer.Invalidate();
            _timer = null;
        }

We have also overriden the viewWillDisappear method, and used it to invalidate the timer, which is not under ARC umbrella.

You have might noticed the updating delegate parameter in initializing _timer variable.

For a better user experience let's update chartView autoresizingMask property as well as axes' growBy property, which adds some padding:

ViewDidLoad()
Copy Code
        public override void ViewDidLoad()
        {
            base.ViewDidLoad();
            // Perform any additional setup after loading the view, typically from a nib.
            _surface = new SCIChartSurface();
            _surface.TranslatesAutoresizingMaskIntoConstraints = true;
            _surface.AutoresizingMask = UIViewAutoresizing.FlexibleDimensions;
            _surface.Frame = this.View.Bounds;
            this.View.AddSubview(_surface);
            _surface.XAxes.Add(new SCINumericAxis() { GrowBy = new SCIDoubleRange(0.1, 0.1) });
            _surface.YAxes.Add(new SCINumericAxis() { GrowBy = new SCIDoubleRange(0.1, 0.1) });
            CreateDataSeries();
            CreateRenderableSeries();
            AddModifiers();
        }

 

Now run the Application. You should see this!

 

Appending Data Values

As well as using DataSeries.Update you can also use DataSeries.Append to add new data-values to a DataSeries. Let's make some changes in our _timer delegate just as follows:

DataSeries.Append usage
Copy Code
        public override void ViewWillAppear(bool animated)
        {
            base.ViewWillAppear(animated);
            if (_timer == null)
            {
                _timer = NSTimer.CreateRepeatingScheduledTimer(0.01, (timer) =>
                {
                    var i = _lineDataSeries.Count;
                    _lineDataSeries.Append(i, Math.Sin(i * 0.1 + _phase));
                    _scatterDataSeries.Append(i, Math.Cos(i * 0.1 + _phase));
                    
                    _phase += 0.01;
                    _surface.ZoomExtents();
                });
            }
        }

 

Now run the application again, you should now see the series growing larger as new data is appended.

 

 

Appending Data Values

What if you wanted to scroll as new data was appended? You have a few choices.

We're going to show you both techniques below.

Discarding Data when Scrolling using FifoCapacity

The most memory efficient way to achieve scrolling is to use DataSeries.FifoCapacity to set the maximum size of a DataSeries before old points are discarded.

DataSeries in FIFO mode act as a circular (first-in-first-out) buffer. Once the capacity is exceeded, old points are discarded.
You cannot zoom back to see the old points, once they are lost, they are lost. To use FifoCapacity, we adjust our CreateDataSeries() method as follows:

CreateDataSeries()
Copy Code
       void CreateDataSeries()
        {
            // Init line data series
            _lineDataSeries = new XyDataSeries<Double, Double>();
            // Setting fifo capacity, once new data values will be added, the old one will be removed
            _lineDataSeries.FifoCapacity = 500;
            // Naming data series, so its name will be shown in LegendModifier
            _lineDataSeries.SeriesName = "LineSeries";
            for (var i = 0; i < 500; i++)
            {
                _lineDataSeries.Append(i, Math.Sin(i * 0.1));
            }
            // Init scatter data series
            _scatterDataSeries = new XyDataSeries<Double, Double>();
            // Setting fifo capacity, once new data values will be added, the old one will be removed
            _scatterDataSeries.FifoCapacity = 500;
            // Naming data series, so its name will be shown in LegendModifier
            _scatterDataSeries.SeriesName = "ScatterSeries";
            for (var i = 0; i < 500; i++)
            {
                _scatterDataSeries.Append(i, Math.Cos(i * 0.1));
            }

            _i = _lineDataSeries.Count;
        }

Take a look at the "_i" property. This is new property we added for counting the last index of element we will be adding. As a quick walkthrough we now create our XyDataSeries and set FifoCapacity = 500.
This tells SciChart after 500 points to start discarded old points (in a first-in-first-out fashion).

Now, the last thing to do is to update the _timer delegate where we are adding new data:

updated _timer delegate
Copy Code
       public override void ViewWillAppear(bool animated)
        {
            base.ViewWillAppear(animated);
            if (_timer == null)
            {
                _timer = NSTimer.CreateRepeatingScheduledTimer(0.01, (timer) =>
                {
                    _i++;
                    _lineDataSeries.Append(_i, Math.Sin(_i * 0.1 + _phase));
                    _scatterDataSeries.Append(_i, Math.Cos(_i * 0.1 + _phase));
                    _phase += 0.01;
                    _surface.ZoomExtents();
                });
            }
        }

This should be the result when you run the application:

 

Preserving Old Data and Allowing Zooming

If you want to be able to retain all data on the chart, as well a zoom backwards and view old data, then you cannot use FifoCapacity.
Instead, we will use another technique. Undo the changes you made above. First, remove the FIFO capacity from your createDataSeries() method.

CreateDataSeries()
Copy Code
       void CreateDataSeries()
        {
            // Init line data series
            _lineDataSeries = new XyDataSeries<Double, Double>();
            // REMOVE THIS LINE
            _lineDataSeries.FifoCapacity = 500;
            // Naming data series, so its name will be shown in LegendModifier
            _lineDataSeries.SeriesName = "LineSeries";
            for (var i = 0; i < 500; i++)
            {
                _lineDataSeries.Append(i, Math.Sin(i * 0.1));
            }
            // Init scatter data series
            _scatterDataSeries = new XyDataSeries<Double, Double>();
            // REMOVE THIS LINE
            _scatterDataSeries.FifoCapacity = 500;
            // Naming data series, so its name will be shown in LegendModifier
            _scatterDataSeries.SeriesName = "ScatterSeries";
            for (var i = 0; i < 500; i++)
            {
                _scatterDataSeries.Append(i, Math.Cos(i * 0.1));
            }
            _i = _lineDataSeries.Count;
        }

The result should be like in the following gif: