Note that there are some explanatory texts on larger screens.

plurals
  1. POaChartEngine: how to display real-time updates
    primarykey
    data
    text
    <p>I'm currently using achartengine to display real-time data received using bluetooth. The data is correctly collected on a specific thread and sent ever 100ms in a Bundle to my main activity.</p> <p>The main activity contains 4 charts from the achartengine library (LineChart class), added to its view using GraphicalView</p> <p>To summarize, every 100ms, my main activity receives a callback and</p> <ul> <li>copy the data from the bundle</li> <li>add it in 4 different datasets (XYMultipleSeriesDataset)</li> <li>call repaint() on the 4 charts containing the datasets</li> </ul> <p>When calling repaint, the datasets won't be updated until the next bundle arrives from the other thread, 100ms later. I assume 100 ms should be more than enough to redraw the views. </p> <p>I post here a small version of the class that holds the datasets and view, and parts of the main activity where it is instanciated and used</p> <pre><code>/** This class contains the data and renderer used to draw a chart */ public class LineChartData { public static enum Series {X, Y, Z}; public static final int MAX_NB_VALUES_PER_SERIE = 1000; private XYMultipleSeriesDataset mDataset = new XYMultipleSeriesDataset(); private XYMultipleSeriesRenderer mRenderer = new XYMultipleSeriesRenderer(); private int[] mColors = new int[]{Color.RED, Color.GREEN, Color.BLUE}; private GraphicalView mChartView; private boolean mFollow = true; private boolean mTouchedDown = false; public LineChartData(Context context, String title, String[] seriesNames) { // Since we save only 3 colors, the programm will not support more than 3 series per chart for(int i = 0; i &lt; seriesNames.length; ++i) { XYSeries serie = new XYSeries(seriesNames[i].toString()); mDataset.addSeries(serie); XYSeriesRenderer serieRenderer = new XYSeriesRenderer(); serieRenderer.setColor(mColors[i]); mRenderer.addSeriesRenderer(serieRenderer); } mRenderer.setMargins(new int[] { 20, 30, 0, 0 }); mRenderer.setLegendHeight(50); //mRenderer.setShowLegend(true); mRenderer.setChartTitle(title); mRenderer.setMarginsColor(Color.WHITE); mRenderer.setXAxisMax(SettingsActivity.DEFAULT_DISPLAY_RANGE); mRenderer.setXAxisMin(0); mChartView = ChartFactory.getLineChartView(context, mDataset, mRenderer); mChartView.repaint(); } public void addData(int serieId, double x, double y) { mDataset.getSeriesAt(serieId).add(x, y); } public void repaint() { mChartView.invalidate(); } public void updateDisplayWindow(int displayRange) { if(!mFollow) { return; } double maxX = mDataset.getSeries()[0].getMaxX(); if(maxX &gt; displayRange) { mRenderer.setXAxisMax(maxX); mRenderer.setXAxisMin(maxX-displayRange); } else { mRenderer.setXAxisMax(displayRange); mRenderer.setXAxisMin(0); } } public void deleteOldestValues() { XYSeries[] series = mDataset.getSeries(); for(XYSeries serie : series) { while(serie.getItemCount() &gt; MAX_NB_VALUES_PER_SERIE) { serie.remove(0); } } } } </code></pre> <p>Creating the 4 charts: </p> <pre><code>protected void onCreate(Bundle b) { mAccelChartView = new LineChartData(this, getString(R.string.Accel), new String[] {getString(R.string.xAxis), getString(R.string.zAxis), getString(R.string.zAxis)}); mGyroChartView = new LineChartData(this, getString(R.string.Gyro), new String[] {getString(R.string.xAxis), getString(R.string.zAxis), getString(R.string.zAxis)}); mPressureChartView = new LineChartData(this, getString(R.string.Pressure), new String[] {getString(R.string.pressureAxis)}); mEcgChartView = new LineChartData(this, getString(R.string.ECG), new String[] {getString(R.string.ecgAxis)}); LinearLayout layoutAccel = (LinearLayout) findViewById(R.id.accelChart); LinearLayout layoutGyro = (LinearLayout) findViewById(R.id.gyroChart); LinearLayout layoutPressure = (LinearLayout) findViewById(R.id.pressureChart); LinearLayout layoutEcg = (LinearLayout) findViewById(R.id.ecgChart); layoutAccel.addView(mAccelChartView.getView(), 0); layoutGyro.addView(mGyroChartView.getView(), 0); layoutPressure.addView(mPressureChartView.getView(), 0); layoutEcg.addView(mEcgChartView.getView(), 0); } </code></pre> <p>Updating the charts</p> <pre><code>private void updateViewUsingMessage(String action, Bundle newData) { if(action.equals(BluetoothCollecterThread.MSG_UPDATE_CHART)) { addFromBundle(newData, mLogger.isLogging()); mAccelChartView.updateDisplayWindow(mDisplayRange); mAccelChartView.deleteOldestValues(); mGyroChartView.updateDisplayWindow(mDisplayRange); mGyroChartView.deleteOldestValues(); mPressureChartView.updateDisplayWindow(mDisplayRange); mPressureChartView.deleteOldestValues(); mEcgChartView.updateDisplayWindow(mDisplayRange); mEcgChartView.deleteOldestValues(); mAccelChartView.repaint(); mGyroChartView.repaint(); mPressureChartView.repaint(); mEcgChartView.repaint(); } } </code></pre> <p>It might seem big but it's actually quite easy. I have two problems with this, I don't know if thay are linked: Firstly, several times per seconds, the first serie from one chart is drawn on another chart (see pictures below). I checked my code and couldn't find any solution. Moreover, it really seems to happen at random so it looks more like a concurrent issue comming from the achartengine library.</p> <p>This bring my second issues. The log shows way too many messages from the garbage collector, especially messages of type WAIT_FOR_CONCURRENT_GC blocked, which is obviously bad.</p> <p>I found this link: <a href="http://stackoverflow.com/questions/14187716/is-achartengine-ready-for-realtime-graphing">http://stackoverflow.com/questions/14187716/is-achartengine-ready-for-realtime-graphing</a> suggest that memory allocation problems remains in achartengine, but that the last version should be sufficient to display a few graphs with 1000 points each in real time. Even when I set the data update to 1 second instead of 100ms I still get the visual glitch</p> <p>The correct behaviour: 4 charts updated in real time (two first with 3 series, two last with 1 serie) <a href="http://s903.photobucket.com/user/bperreno/media/correct_zpsebd731f6.png.html?sort=3&amp;o=1" rel="nofollow noreferrer">http://s903.photobucket.com/user/bperreno/media/correct_zpsebd731f6.png.html?sort=3&amp;o=1</a></p> <p>Wrong: the red serie from chart 2 is displayed on the other charts <a href="http://s903.photobucket.com/user/bperreno/media/wrong1_zpsd33eec83.png.html?sort=3&amp;o=0" rel="nofollow noreferrer">http://s903.photobucket.com/user/bperreno/media/wrong1_zpsd33eec83.png.html?sort=3&amp;o=0</a></p> <p>The data is moving in realtime, but from time to time, the glitch shown in the second picture happen, juste for a frame or two, and then disapear. Does anyone have an idea how to, at least, fix the display problem? and maybe hopefully tell me how to solve the overuse of garbage collection from achartengine</p> <p>Thanks a lot!</p> <p><strong>EDIT 04.12.2013</strong> I'm now using the achartengine sources instead of the .jar so I'm able to look at the values. After seeing that "setInScroll" didn't work, I took a look in the code. In GraphicalView.onDraw method, I compared the values used when setInScroll is true or false.</p> <pre><code>int top = mRect.top; int left = mRect.left; int width = mRect.width(); int height = mRect.getheight(); if(mRenderer.isInScroll()) { top = 0; left = 0; width = getMeasuredWidth(); height = getMeasuredHeight(); } </code></pre> <p>The values are always the same wither comming from mRect, or from getMeasuredXXX(). top and left are zeros in both cases. So (in this case at least), inScroll = true has no effect</p>
    singulars
    1. This table or related slice is empty.
    plurals
    1. This table or related slice is empty.
    1. This table or related slice is empty.
    1. This table or related slice is empty.
    1. This table or related slice is empty.
 

Querying!

 
Guidance

SQuiL has stopped working due to an internal error.

If you are curious you may find further information in the browser console, which is accessible through the devtools (F12).

Reload