Quick Start Guide > Tutorials (Java Android) > Tutorial 06 - Adding Realtime Updates
Tutorial 06 - Adding Realtime Updates

So far we have focused on static charts. Assuming you have completed Tutorial 05 - Adding Tooltips and Legends, now we will show how to create live charts.

Source code for this tutorial can be found at SciChart.Android.Examples Github Repository

Adding New Data Values

We change the way we populate lineData and scatterData by spinning up a new thread and using java.util.Timer to update it in the background.

Copy Code
 final XyDataSeries lineData = sciChartBuilder.newXyDataSeries(Integer.class, Double.class).build();
    final XyDataSeries scatterData = sciChartBuilder.newXyDataSeries(Integer.class, Double.class).build();
  
    // Add new data on update
    TimerTask updateDataTask = new TimerTask() {
    @Override
    public void run() {
        UpdateSuspender.using(surface, new Runnable() {
            @Override
            public void run() {
                int x = lineData.getCount();
                lineData.append(x, Math.sin(x * 0.1));
                scatterData.append(x, Math.cos(x * 0.1));
                // Zoom series to fit the viewport
                surface.zoomExtents();
            }
        });
    }
    };
    Timer timer = new Timer();
    long delay = 0;
    long interval = 10;
    timer.schedule(updateDataTask, delay, interval);

Making the Code More Efficient

This code works, but it isn't very efficient:

  • Calling an UpdateXXX method triggers a chart update, which redraws the entire chart.
  • The values are passed in as Double objects, which require boxing/unboxing, which increases the number of objects to be removed during Garbage Collection.

Here is how to improve that:

  • Always append or update data in a DataSeries in batches instead of one at a time.
  • Suspend updates on a SciChartSurface using UpdateSuspender to prevent redrawing until you have updated the whole DataSeries.
  • Use the class DoubleValues. It stores data in an array internally and requires not require boxing/unboxing.

So we rewrite the code as follows:

Copy Code
    final XyDataSeries lineData = sciChartBuilder.newXyDataSeries(Integer.class, Double.class).build();
    final XyDataSeries scatterData = sciChartBuilder.newXyDataSeries(Integer.class, Double.class).build();
    final int dataCount = 1000;
    for (int i = 0; i < dataCount; i++)
    {
    lineData.append(i, Math.sin(i * 0.1));
    scatterData.append(i, Math.cos(i * 0.1));
    }
 
    final DoubleValues lineDoubleData = new DoubleValues(dataCount);
    final DoubleValues scatterDoubleData = new DoubleValues(dataCount);
    lineDoubleData.setSize(dataCount);
    scatterDoubleData.setSize(dataCount);
    TimerTask updateDataTask = new TimerTask() {
    private double _phaseShift = 0.0;
    @Override
    public void run() {
        UpdateSuspender.using(surface, new Runnable() {
            @Override
            public void run() {
                // Fill the DoubleValues collections
                for (int i = 0; i < dataCount; i++)
                {
                    lineDoubleData.set(i, Math.sin(i * 0.1 + _phaseShift));
                    scatterDoubleData.set(i, Math.cos(i * 0.1 + _phaseShift));
                }
                // Update DataSeries using bunch update
                lineData.updateRangeYAt(0, lineDoubleData);
                scatterData.updateRangeYAt(0, scatterDoubleData);
            }
        });
        _phaseShift += 0.01;
    }
    };
    Timer timer = new Timer();
    long delay = 0;
    long interval = 10;
    timer.schedule(updateDataTask, delay, interval);

Also, we should mention that it is worth rewriting initial call to the append() method the same way. This will result in a noticeable decrease in load time in when there is a large amount of data.

Finally, you should end up with a chart looking like this:

 

The resulting chart should look very much like this:

 

Scrolling Realtime (FIFO) 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 of an axis.

We're going to show you both techniques below.

FIFO Scrolling Charts (Discarding Old Data)

The most memory efficient way to achieve scrolling is to call setFifoCapacity() on a DataSeries 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, you have to set fifo capacity on the DataSeries, which defines the size of the FIFO buffer. The code from our application should be rewritten like the following to utilize a FIFO buffer:

Copy Code
// Set FIFO capacity to 500 on DataSeries
    final int fifoCapacity = 500;
    final XyDataSeries lineData = sciChartBuilder.newXyDataSeries(Integer.class, Double.class)
        .withFifoCapacity(fifoCapacity)
        .build();
       
    final XyDataSeries scatterData = sciChartBuilder.newXyDataSeries(Integer.class, Double.class)
        .withFifoCapacity(fifoCapacity)
        .build();
        TimerTask updateDataTask = new TimerTask() {
    private int x = 0;
    @Override
    public void run() {
        UpdateSuspender.using(surface, new Runnable() {
            @Override
            public void run() {
                lineData.append(x, Math.sin(x * 0.1));
                scatterData.append(x, Math.cos(x * 0.1));
                // Zoom series to fit the viewport
                surface.zoomExtents();
                ++x;
            }
        });
    }
    };
    Timer timer = new Timer();
    long delay = 0;
    long interval = 10;
    timer.schedule(updateDataTask, delay, interval);

 

The code for the most part is self-explanatory. But we can point that that we set. FifoCapacity on the DataSeries. Then, after apending new data we call zoomExtents() to make series to fit the viewport in Y direction.

This should be the result when you run the application:

 

See Also