aChartEngine: как отображать обновления в реальном времени
В настоящее время я использую achartengine для отображения данных в реальном времени, полученных с помощью Bluetooth. Данные правильно собираются в определенном потоке и отправляются каждые 100 мс в Bundle для моей основной деятельности.
Основное действие содержит 4 графика из библиотеки achartengine (класс LineChart), добавленных в ее представление с помощью GraphicalView.
Подводя итог, каждые 100 мс моя основная деятельность получает обратный вызов и
- скопировать данные из пакета
- добавить его в 4 разных наборах данных (XYMultipleSeriesDataset)
- вызовите repaint() на 4 графиках, содержащих наборы данных
При вызове repaint наборы данных не будут обновляться до тех пор, пока следующий пакет не прибудет из другого потока, спустя 100 мс. Я предполагаю, что 100 мс должно быть более чем достаточно, чтобы перерисовать представления.
Я публикую здесь небольшую версию класса, которая содержит наборы данных и представление, а также части основного вида деятельности, где он создается и используется
/** 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 < 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 > 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() > MAX_NB_VALUES_PER_SERIE)
{
serie.remove(0);
}
}
}
}
Создание 4 графиков:
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);
}
Обновление графиков
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();
}
}
Это может показаться большим, но на самом деле это довольно просто. У меня есть две проблемы с этим, я не знаю, связаны ли они: во-первых, несколько раз в секунду первая серия с одного графика рисуется на другом графике (см. Рисунки ниже). Я проверил свой код и не смог найти никакого решения. Более того, это действительно кажется случайным, поэтому это больше похоже на параллельную проблему, исходящую из библиотеки achartengine.
Это приносит мои вторые проблемы. Журнал показывает слишком много сообщений от сборщика мусора, особенно сообщения типа WAIT_FOR_CONCURRENT_GC заблокированы, что, очевидно, плохо.
Я нашел эту ссылку: /questions/27176110/gotov-li-achartengine-dlya-postroeniya-grafikov-v-realnom-vremeni предполагает, что проблемы с выделением памяти остаются в achartengine, но последней версии должно быть достаточно для отображения нескольких графиков. с 1000 очков каждый в режиме реального времени. Даже когда я устанавливаю обновление данных на 1 секунду вместо 100 мс, я все равно получаю визуальный сбой
Правильное поведение: 4 диаграммы обновляются в режиме реального времени (две первые с 3 сериями, две последние с 1 серией) http://s903.photobucket.com/user/bperreno/media/correct_zpsebd731f6.png.html?sort=3&o=1
Неправильно: красная серия из диаграммы 2 отображается на других диаграммах http://s903.photobucket.com/user/bperreno/media/wrong1_zpsd33eec83.png.html?sort=3&o=0
Данные перемещаются в режиме реального времени, но время от времени сбой, показанный на втором изображении, происходит, оправдывает кадр или два, а затем исчезает. У кого-нибудь есть идеи, как, по крайней мере, исправить проблему с дисплеем? и, возможно, надеюсь, скажите мне, как решить проблему чрезмерного использования сборки мусора от achartengine
Большое спасибо!
РЕДАКТИРОВАТЬ 04.12.2013 Теперь я использую источники achartengine вместо.jar, поэтому я могу посмотреть на значения. Увидев, что setInScroll не работает, я взглянул на код. В методе GraphicalView.onDraw я сравнил значения, используемые, когда setInScroll имеет значение true или false.
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();
}
Значения всегда одинаковы при получении из mRect или из getMeasuredXXX(). сверху и слева нули в обоих случаях. Таким образом (по крайней мере, в этом случае), inScroll = true не имеет никакого эффекта
2 ответа
Хорошо, я отвечаю на мой вопрос:
После нескольких тестов я обнаружил, что это определенно сопутствующая проблема, которая возникает, когда выполняется слишком много операций над библиотекой achartengine.
Тяжелые операции, кажется, включают
- удаление точки
- настройка X max отображаемое значение
- освежающий
Таким образом, лучшее, что я мог сделать, чтобы уменьшить проблемы, это установить максимальную позицию X дальше, поэтому мне нужно обновлять ее только время от времени (например, один раз в секунду)
Добавление метода в библиотеку, позволяющее удалять много точек одновременно, также может быть полезным. Удаление в настоящее время составляет O(n), а удаление m точек один за другим, таким образом, составляет O (нм).
Обратите внимание, что более мощные устройства, чем моя Galaxy S2 (например, S4mini), справляются с дисплеем без сбоев. Но эффективность остается проблемой для большего числа графиков и более высокой частоты.
достижение 10 FPS для 4 графиков не совсем то, что я бы назвал в реальном времени
Проблема с отображением может исчезнуть, вызвав:
mRenderer.setInScroll(true);
Вы не упомянули версию AChartEngine, которую вы используете.