Search Results for

    Show / Hide Table of Contents

    SciChart Android Tutorial - Adding Realtime Updates

    In the previous tutorials we've showed how to Create a Simple Chart, add some Zoom and Pan interaction as well as Tooltips Inspection + Legends via the Chart Modifiers API.

    In this SciChart Android tutorial we're going to go a little further and show how to update data displayed by a chart in real-time.

    Getting Started

    This tutorial is suitable for Java and Kotlin.

    Note

    Source code for this tutorial can be found at our Github Repository: Java and Kotlin Tutorials Repository

    Assuming you have completed the previous tutorial, we will now make some changes to update the data dynamically.

    Updating Data Values

    In our IDataSeries<TX,TY>, we have some static data so far. Let's update them in real-time now.

    We are going to add a Timer and schedule updating the data on timer tick. To update data in a DataSeries, we will need to call one of the available Update methods on that DataSeries. Since we are using XyDataSeries<TX,TY>, we are going to use the updateXyAt(int index, TX x, TY y) method.

    More information about Updating DataSeries can be found in the Manipulating DataSeries Data article.

    But first of all, we need to adjust some previously created code and save DataSeries instances to be able update them later. And since we are going to change a DataSeries setup, it worth mentioning that the code from the previous tutorials works, but it wasn't very efficient:

    • Calling any of the Update methods triggers a chart update, which redraws the entire chart.
    • The values are passed in as Number objects, which require boxing/unboxing, which slows down the process as well

    No worries, in SciChart there is an easy way to improve that:

    • Make sure to always Append or Update data in a DataSeries in batches instead of one at a time.
    • Suspend updates on a SciChartSurface using using(ISuspendable suspendable, Runnable runnable) to prevent redrawing until you have updated the whole DataSeries.
    • Use one of the IValues<T> implementation such as DoubleValues. It stores data in an primitive array internally and doesn't requires boxing/unboxing.

    So we updated the code as follows:

    • Java
    • Java with Builders API
    • Kotlin
    • Xamarin.Android
    private int pointsCount = 200;
    
    private ScheduledExecutorService scheduledExecutorService = Executors.newSingleThreadScheduledExecutor();
    private ScheduledFuture schedule;
    
    private SciChartSurface surface;
    
    private final DoubleValues lineData = new DoubleValues();
    private XyDataSeries lineDataSeries;
    
    private DoubleValues scatterData = new DoubleValues();
    private XyDataSeries scatterDataSeries;
    

    // ...

    final IntegerValues xValues = new IntegerValues();
    for (int i = 0; i < pointsCount; i++) {
        xValues.add(i);
        lineData.add(Math.sin(i * 0.1));
        scatterData.add(Math.cos(i * 0.1));
        count += 1;
    }
    lineDataSeries.append(xValues, lineData);
    scatterDataSeries.append(xValues, scatterData);
    
    private int pointsCount = 200;
    
    private ScheduledExecutorService scheduledExecutorService = Executors.newSingleThreadScheduledExecutor();
    private ScheduledFuture schedule;
    
    private SciChartSurface surface;
    
    private final DoubleValues lineData = new DoubleValues();
    private XyDataSeries lineDataSeries;
    
    private DoubleValues scatterData = new DoubleValues();
    private XyDataSeries scatterDataSeries;
    

    // ...

    final IntegerValues xValues = new IntegerValues();
    for (int i = 0; i < pointsCount; i++) {
        xValues.add(i);
        lineData.add(Math.sin(i * 0.1));
        scatterData.add(Math.cos(i * 0.1));
        count += 1;
    }
    lineDataSeries.append(xValues, lineData);
    scatterDataSeries.append(xValues, scatterData);
    
    private val pointsCount = 200
    
    private val scheduledExecutorService = Executors.newSingleThreadScheduledExecutor()
    private lateinit var schedule: ScheduledFuture<*>
    
    private lateinit var surface: SciChartSurface
    
    private val lineData = DoubleValues()
    private val lineDataSeries = XyDataSeries(Int::class.javaObjectType, Double::class.javaObjectType)
    
    private val scatterData = DoubleValues()
    private val scatterDataSeries = XyDataSeries(Int::class.javaObjectType, Double::class.javaObjectType)
    

    // ...

    val xValues = IntegerValues()
    for (i in 0 until pointsCount) {
        xValues.add(i)
        lineData.add(sin(i * 0.1))
        scatterData.add(cos(i * 0.1))
        count += 1
    }
    lineDataSeries.append(xValues, lineData)
    scatterDataSeries.append(xValues, scatterData)
    
    private const int PointsCount = 200;
    private const long TimerInterval = 10;
    
    private Timer _timer;
    
    private SciChartSurface surface;
    
    private readonly DoubleValues lineData = new DoubleValues();
    private readonly XyDataSeries<int, double> lineDataSeries = new XyDataSeries<int, double>() { SeriesName = "Line Series" };
    
    private readonly DoubleValues scatterData = new DoubleValues();
    private readonly XyDataSeries<int, double> scatterDataSeries = new XyDataSeries<int, double>() { SeriesName = "Scatter Series" };
    

    // ...

    var xValues = new IntegerValues();
    
    for (int i = 0; i < PointsCount; i++) 
    {
        xValues.Add(i);
        lineData.Add(Math.Sin(i * 0.1));
        scatterData.Add(Math.Cos(i * 0.1));
        count += 1;
    }  
    lineDataSeries.Append(xValues, lineData);
    scatterDataSeries.Append(xValues, scatterData);
    

    From here, we can initialize our Timer and create an updateData runnable, with real-time updates, like follows:

    • Java
    • Java with Builders API
    • Kotlin
    • Xamarin.Android
    schedule = scheduledExecutorService.scheduleWithFixedDelay(updateData, 0, 10, TimeUnit.MILLISECONDS);
    

    // ...

    private Double phase = 0.0;
    
    private final Runnable updateData = () -> {
        for (int i = 0; i < pointsCount; i++) {
            lineData.set(i, Math.sin(i* 0.1 + phase));
            scatterData.set(i, Math.cos(i* 0.1 + phase));
        }
    
        UpdateSuspender.using(surface, () -> {
            lineDataSeries.updateRangeYAt(0, lineData);
            scatterDataSeries.updateRangeYAt(0, scatterData);
    
            // zoom series to fit viewport size into X-Axis direction
            surface.zoomExtentsX();
        });
    
        phase += 0.01;
    };
    
    schedule = scheduledExecutorService.scheduleWithFixedDelay(updateData, 0, 10, TimeUnit.MILLISECONDS);
    

    // ...

    private Double phase = 0.0;
    
    private final Runnable updateData = () -> {
        for (int i = 0; i < pointsCount; i++) {
            lineData.set(i, Math.sin(i* 0.1 + phase));
            scatterData.set(i, Math.cos(i* 0.1 + phase));
        }
    
        UpdateSuspender.using(surface, () -> {
            lineDataSeries.updateRangeYAt(0, lineData);
            scatterDataSeries.updateRangeYAt(0, scatterData);
    
            // zoom series to fit viewport size into X-Axis direction
            surface.zoomExtentsX();
        });
    
        phase += 0.01;
    };
    
    schedule = scheduledExecutorService.scheduleWithFixedDelay(updateData, 0, 10, TimeUnit.MILLISECONDS)
    

    // ...

    private var phase: Double = 0.0
    
    private val updateData = Runnable {
        for (i in 0 until pointsCount) {
            lineData.set(i, sin(i* 0.1 + phase))
            scatterData.set(i, cos(i* 0.1 + phase))
        }
    
        UpdateSuspender.using(surface) {
            lineDataSeries.updateRangeYAt(0, lineData)
            scatterDataSeries.updateRangeYAt(0, scatterData)
    
            // zoom series to fit viewport size into X-Axis direction
            surface.zoomExtentsX()
        }
        phase += 0.01
    }
    
    _timer = new Timer(TimerInterval) { AutoReset = true };
    _timer.Elapsed += OnTick;
    _timer.Start();
    

    // ...

    private Double phase = 0.0;
    
    private void OnTick(object sender, ElapsedEventArgs e)
    {
        lock (_syncRoot)
        {
            for (int i = 0; i < PointsCount; i++)
            {
                lineData.Set(i, Math.Sin(i * 0.1 + phase));
                scatterData.Set(i, Math.Cos(i * 0.1 + phase));
            }
            
            using (surface.SuspendUpdates())
            {
                lineDataSeries.UpdateRangeYAt(0, lineData);
                scatterDataSeries.UpdateRangeYAt(0, scatterData);
    
                // zoom series to fit viewport size into X-Axis direction
                surface.ZoomExtentsX();
            }
    
            phase += 0.01;
        }
    }
    

    Which will result in the following Chart:

    Note

    Despite the chart is now real-time, it's still fully interactive, you can use modifiers from previous tutorials with ease.

    Adding New Data Values

    As well as using updateXyAt(int index, TX x, TY y), you can also use append(TX x, TY y) (or any other available Append method) to add new data-values to a DataSeries.

    The code from above can be updated as follows to append new data constantly to the dataSeries:

    • Java
    • Java with Builders API
    • Kotlin
    • Xamarin.Android
    private Integer count = 0;
    
    private final Runnable updateData = () -> {
        Integer x = count;
        UpdateSuspender.using(surface, () -> {
            lineDataSeries.append(x, Math.sin(x* 0.1));
            scatterDataSeries.append(x, Math.cos(x * 0.1));
    
            // zoom series to fit viewport size into X-Axis direction
            surface.zoomExtentsX();
            count += 1;
        });
    };
    
    private Integer count = 0;
    
    private final Runnable updateData = () -> {
        Integer x = count;
        UpdateSuspender.using(surface, () -> {
            lineDataSeries.append(x, Math.sin(x* 0.1));
            scatterDataSeries.append(x, Math.cos(x * 0.1));
    
            // zoom series to fit viewport size into X-Axis direction
            surface.zoomExtentsX();
            count += 1;
        });
    };
    
    private var count: Int = 0
    
    private val updateData = Runnable {
        val x = count
        UpdateSuspender.using(surface) {
            lineDataSeries.append(x, sin(x* 0.1))
            scatterDataSeries.append(x, cos(x * 0.1))
    
            // zoom series to fit viewport size into X-Axis direction
            surface.zoomExtentsX()
            count += 1
        }
    }
    
    private int count = 0;
    
    private void OnTick(object sender, ElapsedEventArgs e)
    {
        lock (_syncRoot)
        {
            var x = count;
            using (surface.SuspendUpdates())
            {
                lineDataSeries.Append(x, Math.Sin(x * 0.1));
                scatterDataSeries.Append(x, Math.Cos(x * 0.1));
    
                count += 1;
                surface.ZoomExtentsX();
            }
        }
    }
    

    Scrolling Realtime Charts

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

    • If you want to be memory efficient, and you don't mind if you discard old data, you can use our FIFO (first-in-first-out) functionality.
    • If you want to preserve old data, you can simply update the visibleRange.

    Since updating VisibleRange is fairly self-explanatory, we are going to explain the FIFO method.

    Discarding Data when Scrolling using FifoCapacity

    The most memory efficient way to achieve scrolling is to use 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 make a DataSeries use the FIFO buffer, all you need to do is just set fifo capacity on the DataSeries, e.g.:

    • Java
    • Java with Builders API
    • Kotlin
    • Xamarin.Android
    lineDataSeries.setFifoCapacity(300);
    scatterDataSeries.setFifoCapacity(300);
    
    lineDataSeries = sciChartBuilder.newXyDataSeries(Integer.class, Double.class)
            .withSeriesName("Line Series")
            .withFifoCapacity(300)
            .build();
    scatterDataSeries = sciChartBuilder.newXyDataSeries(Integer.class, Double.class)
            .withSeriesName("Scatter Series")
            .withFifoCapacity(300)
            .build();
    
    lineDataSeries.fifoCapacity = 300
    scatterDataSeries.fifoCapacity = 300
    
    lineDataSeries.FifoCapacityValue = 300;
    scatterDataSeries.FifoCapacityValue = 300;
    
    Note

    After appending new data we call zoomExtents to make series to fit the viewport.

    The following should be the result when you run the application:

    Where to Go From Here?

    You can download the final project from our Java and Kotlin Tutorials Repository.

    Also, you can found next tutorial from this series here - SciChart Android Tutorial - Annotations

    Of course, this is not the limit of what you can achieve with the SciChart Android. Our documentation contains lots of useful information, some of the articles you might want to read are listed below:

    • Axis Types
    • 2D Chart Types
    • Chart Modifiers

    Finally, start exploring. The SciChart Android library and functionality is quite extensive. You can look into our SciChart Android Examples Suite which are full of 2D and 3D examples, which are also available on our GitHub

    In particular, you might want to take a look at our Fifo Scrolling Chart:

    Back to top © 2011-2025 SciChart. All rights reserved. | sitemap.xml