I'm writing a real time graphing app using Scichart for android. I've been using
FastLineRenderableSeries as a wrapper for my data series
But I'm wondering what other techniques with Android SciChart exist in order to maximize graphing speed?
Particularly I've noticed a decrease in performance when I use IXyDataSeries and increase the x axis size to 100,000 pts from 10,000. The speed of the graphing stays consistently fast until I've added about 90,000 points to my IXyDataSeries.
Thanks guys. I'm new to stackoverflow... more of a mechE than a CS person.
Here is my graphFragment class which takes in UDP sensor data as a string, splices it and adds it to the IXyDataSeries.
public class GraphFragment extends Fragment {
//Various fields...
//UDP Settings
private UdpClient client;
private String hostname;
private int remotePort;
private int localPort;
//Use to communicate with UDPDataClass
private Handler handler;
private boolean listenerExists = false;
private int xBound = 100000; //**Graphing Slows if xBound is TOO large**
private int yBound = 5000;
private boolean applyBeenPressed = false;
private GraphDataSource dataSource; //Gets data from UDPDataClass
private SciChartSurface plotSurface; //Graphing Surface
protected final SciChartBuilder sciChartBuilder = SciChartBuilder.instance();
//Data Series containers
//Perhaps it would be better to use XyySeries here?
private final IXyDataSeries<Double, Double> dataSeriesSensor1 = sciChartBuilder.newXyDataSeries(Double.class, Double.class).build();
private final IXyDataSeries<Double, Double> dataSeriesSensor2 = sciChartBuilder.newXyDataSeries(Double.class, Double.class).build();
private final IXyDataSeries<Double, Double> dataSeriesSensor3 = sciChartBuilder.newXyDataSeries(Double.class, Double.class).build();
private final IXyDataSeries<Double, Double> dataSeriesSensor4 = sciChartBuilder.newXyDataSeries(Double.class, Double.class).build();
private final IXyDataSeries<Double, Double> dataSeriesSensor5 = sciChartBuilder.newXyDataSeries(Double.class, Double.class).build();
private final IXyDataSeries<Double, Double> dataSeriesSensor6 = sciChartBuilder.newXyDataSeries(Double.class, Double.class).build();
private ArrayList<IXyDataSeries<Double,Double>> dataSeriesList = new ArrayList<>(Arrays.asList(dataSeriesSensor1,dataSeriesSensor2,dataSeriesSensor3,dataSeriesSensor4, dataSeriesSensor5, dataSeriesSensor6));
private ArrayList<Double> xCounters = new ArrayList<>(Arrays.asList(0.0,0.0,0.0,0.0,0.0,0.0));
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
final View frag = inflater.inflate(R.layout.graph_fragment, container, false);
plotSurface = (SciChartSurface) frag.findViewById(R.id.dynamic_plot);
dataSource = new GraphDataSource(); //Run the data handling on a separate thread
dataSource.start();
UpdateSuspender.using(plotSurface, new Runnable() {
@Override
public void run() {
final NumericAxis xAxis = sciChartBuilder.newNumericAxis().withVisibleRange(0,xBound).build();
final NumericAxis yAxis = sciChartBuilder.newNumericAxis().withVisibleRange(0,yBound).build();
//These are wrappers for the series we will add the data to...It contains the formatting
final FastLineRenderableSeries rs1 = sciChartBuilder.newLineSeries().withDataSeries(dataSeriesSensor1).withStrokeStyle(ColorUtil.argb(0xFF, 0x40, 0x83, 0xB7)).build(); //Light Blue Color
final FastLineRenderableSeries rs2 = sciChartBuilder.newLineSeries().withDataSeries(dataSeriesSensor2).withStrokeStyle(ColorUtil.argb(0xFF, 0xFF, 0xA5, 0x00)).build(); //Light Pink Color
final FastLineRenderableSeries rs3 = sciChartBuilder.newLineSeries().withDataSeries(dataSeriesSensor3).withStrokeStyle(ColorUtil.argb(0xFF, 0xE1, 0x32, 0x19)).build(); //Orange Red Color
final FastLineRenderableSeries rs4 = sciChartBuilder.newLineSeries().withDataSeries(dataSeriesSensor4).withStrokeStyle(ColorUtil.argb(0xFF, 0xFF, 0xFF, 0xFF)).build(); //White color
final FastLineRenderableSeries rs5 = sciChartBuilder.newLineSeries().withDataSeries(dataSeriesSensor5).withStrokeStyle(ColorUtil.argb(0xFF, 0xFF, 0xFF, 0x99)).build(); //Light Yellow color
final FastLineRenderableSeries rs6 = sciChartBuilder.newLineSeries().withDataSeries(dataSeriesSensor6).withStrokeStyle(ColorUtil.argb(0xFF, 0xFF, 0x99, 0x33)).build(); //Light Orange color
Collections.addAll(plotSurface.getXAxes(), xAxis);
Collections.addAll(plotSurface.getYAxes(), yAxis);
Collections.addAll(plotSurface.getRenderableSeries(), rs1, rs2, rs3, rs4, rs5, rs6);
}
});
return frag;
}
//This class receives the UDP sensor data as messages to its handler
//Then it splices the data
//Adds the data to the IXySeries
//Then the UpdateSuspender updates the graph
//New data arrives approx every 50 ms (around 20x a second)
//Graphing slows when xAxis is increased to ~100,000
//X data is only counters...Only care about Y data
public class GraphDataSource extends Thread{
public void run(){
Looper.prepare();
//Get Data from UDP Data Class when its available
handler = new Handler(){
public void handleMessage(Message msg){
String sensorData = msg.getData().getString("data"); //Data receiveds
if(dataValid(sensorData)){
sensorData = sensorData.replaceAll("\\s", "");
final String[] dataSplit = sensorData.split(","); //split the data at the commas
UpdateSuspender.using(plotSurface, new Runnable() { //This updater graphs the values
@Override
public void run() {
spliceDataAndAddData(dataSplit);
}
});
}
}
};
Looper.loop();
}
/**
*
* @param data string of the udp data
* @return true if the data isn't corrupted..aka the correct length
*/
private boolean dataValid(String data){
return ((data.length() == 1350));
}
/**
*
* @param dataSplit String[] of the entire data
* Adds the each sensor data to the IXySeries representing the data
*/
private void spliceDataAndAddData(String[] dataSplit){
addToSensorSeries(dataSplit, 1);
addToSensorSeries(dataSplit, 2);
addToSensorSeries(dataSplit, 3);
addToSensorSeries(dataSplit, 4);
addToSensorSeries(dataSplit, 5);
addToSensorSeries(dataSplit, 6);
}
/**
*
* @param dataSplit data to split into individual sensor array
* must contain only string representations of numbers
* @param sensorSeriesNumber which sensors to collect the data points of
* Adds the data to the corresponding IXySeries
*/
private void addToSensorSeries(String[] dataSplit, int sensorSeriesNumber){
sensorSeriesNumber -= 1; //Adds each value individually to the series
double xcounter = xCounters.get(sensorSeriesNumber);
int i = sensorSeriesNumber;
int dataSize = dataSplit.length - 1;
String num = "";
while(true){
if(i < 6){ //This is the base case...add the first set of data
num = dataSplit[i];
try {
if(xcounter > xBound){
xcounter = 0;
dataSeriesList.get(sensorSeriesNumber).clear();
}
dataSeriesList.get(sensorSeriesNumber).append(xcounter, Double.parseDouble(num)); //appends every number...
}catch (Exception e){
//Corrupt data
}
}else if((i) <= dataSize && i >= 6){ //Will start to get hit after the second time
num = dataSplit[i];
try {
if(xcounter > xBound){
xcounter = 0;
dataSeriesList.get(sensorSeriesNumber).clear();
}
dataSeriesList.get(sensorSeriesNumber).append(xcounter, Double.parseDouble(num));
}catch (Exception e){
//Corrupt data
}
}else{
break;
}
xcounter++;
i += 6;
}
xCounters.set(sensorSeriesNumber,xcounter);
}
}
I took a look on you code and I'm not sure if we can do something about it. Your example contains 6 XyDataSeries with XRange from 0 to 100000 this gives 600 000 points on screen which is pretty good for realtime example on HTC One. In SciChart performance demo you can see usage of only 3 XyDataSeries instances which allows to draw more points in each series
Disclosure: I am the lead developer on the SciChart Android team
But I think you can get few extra FPS by adding some optimizations in your code. The main problem in realtime charts is in code which updates a chart - it is called very often so if you create some objects during update and don't save it then this could cause problem because of GC in Android (GC pass is slow and it can pause all application's threads while GC is collecting all unused objects). So I would suggest you to do next:
dataSeriesList.get(sensorSeriesNumber).append(xcounter, Double.parseDouble(num));
double xcounter = xCounters.get(sensorSeriesNumber);
xCounters.set(sensorSeriesNumber,xcounter);
I would suggest you to use append override which accepts IValues. Usage of append which accepts IValues allows to avoid unnecessary boxing/unboxing of primitive types when you append alot of data very frequently.
Hope this will help you.