How to create multiple charts in a ListView with an adapter


#1

Hello,

I’m a big fan of Shinobi, and have been using it for a long time in my iPhone app, recently bought the package for Android.  I’ve been trying for a while to create an adapter which displays multiple ChartViews via a ListView, and will properly page in the correct view as one scrolls through the list.  I can get the adapter to work fairly well if the ChartViews remain static, however if I change the data and use shinobiChart.redrawChart() the result is incredibly slow and unpredictable.

Can anyone offer some advice on how to properly build an adapter to display these ChartViews?

My current adapter is as such:

@Override
public View getView(int position, View convertView, ViewGroup parent) {
	LayoutInflater inf = (LayoutInflater) getContext()
			.getSystemService(Context.LAYOUT_INFLATER_SERVICE);

	View v = convertView;
	PlotValues pv = items.get(position);
	if (pv != null) {
		v = inf.inflate(R.layout.shinobi_list_plot, null);

		ChartView chart = (ChartView) v.findViewById(R.id.chart_view);
		chart.setLayoutParams(new LinearLayout.LayoutParams(
				LayoutParams.MATCH_PARENT, 250));

		ShinobiChart shinobiChart = chart.getShinobiChart();

		shinobiChart.setTitle(pv.pid.getId());
		pv.setShinobiChart(shinobiChart);

		shinobiChart.setTitle(pv.pid.getDescription());
		shinobiChart.setXAxis(new NumberAxis());
		shinobiChart.setYAxis(new NumberAxis());
		shinobiChart.addSeries(pv.shinobiSeries);
	}
	return v;
}

The PlotValues class which is refrenced (via “pv”) creates a LineSeries, and assigns a SimpleDataAdapter to it, to allow additional points to be added via the dataAdapter.add(DataPoint) method.  It also has a refrence to the ShinobiChart to allow the .redrawChart() method to be called from the updating class.  The updating class calls the update method like so:

private void updateChart(final PlotValues pv, final DataPoint dataPoint) {
	final ListView lv = (ListView) mView.findViewById(R.id.list);
	pv.addDataPoint(dataPoint);

	getActivity().runOnUiThread(new Runnable() {

		@Override
		public void run() {
			if (lv.getAdapter() == null) {
					lv.setAdapter(new ShinobiPlotAdapter(mView
							.getContext(), mPlotValues));
			} else {
				pv.redraw(); //calls shinobiChart.redrawChart directly
			}
		}
	});
}

If anyone could help me out with this it would be a tremendous help!

Thanks!


#2

Hi AEB,

A few suggestions spring to mind, though I haven’t actually built anything identical to this:

Android doesn’t make any guarantees about in what order how often getView is called, so it makes sense to minimise the work done in it, particularly as a chart is quite complex and has much internal state. Given that, I’d suggest a pooling approach. There’s quite a useful blog post here http://android.amberfog.com/?p=296 which explains the general principle, but you’d need to extend the concept a bit for use with the charts.

Basically, you’d keep all the SimpleDataAdapters you need for your full set of graphs (irrespective of what’s visible). You can update all or any of these as your application logic demands, on the UI thread as you are doing, even if they’re not currently attached to a chart. 

Looking at the code above the charts seem to be more or less interchangeable, with 2 axes and a line series, so you can keep a pool of them. When one comes into view, attach the appropriate SimpleDataAdapter to it (and do whatever else you need to do to the chart), and remove it when the chart goes out of view. It shouldn’t be necessary to call redrawChart explicitly as both setting the data adapter and adding a data point will trigger a reload and redraw.

I’d also advise profiling your app to see how often getView actually does get called, and how often you are setting or updating data adapters attached to a view, particularly if the data adapters are holding a lot of data.

The CustomDataAdapter sample shows one pattern for dynamically updating data, which may improve performance if you’re throwing a lot of data at it fast.

Best regards

Robin Sillem

Lead Developer

ShinobiControls


#3

Thanks for the suggestion Robin.  Simply removing the call to redraw seems to have solved it in itinitial testing, fingers crossed that it was the root cause!