2

I have a LineChart with a single data set (usually 512 points). I need to replace the current data with new data and redraw the chart as quickly as possible, changing nothing else.

I am trying to understand the way the library works in order to optimise the update of the chart.

If I were to update the data set as follows:

lineDataSet.setValues(newEntries); // newEntries is List<Entry> w/new data
chart.invalidate(); //what are the intermediate steps here

it is not clear what intermediate steps are invoked and if they incur a performance cost. What are the performance bottlenecks in updating the chart like this? Can I overcome them by multithreading?

David Rawson
  • 20,912
  • 7
  • 88
  • 124
Robert Lewis
  • 1,847
  • 2
  • 18
  • 43

1 Answers1

2

Start by having a look at the DataSet and BaseDataSet classes in the MPAndroidChart source.

The DataSet is backed by a simple list of Entry. Note also that MPAndroidChart needs to know the xMin, xMax, yMin and yMax for its own internal calculations and these are encapsulated in the DataSet object.

Every time you call notifyDataSetChanged() the min/max are recalculated inside the DataSet object which involves iterating over the entire backing list of Entry. Moreover, adding an unordered Entry will trigger iteration of the backing list to find the correct position in the list. Removal of an Entry will likewise trigger iteration of the backing list. In short, the DataSet object is optimised for adding ordered Entry only.

If you suspect that creation of new DataSet objects with 512 Entry is a bottleneck and could be optimised by offloading to another thread, I suggest you write a microbenchmark to check the time taken for recreating the DataSet object and adding the Entry you want. While new object allocation in Android is expensive, the actual iteration of the Entry list in order to calculate the min/max is unlikely to be expensive because of CPU caching and spatial locality. If you want to make the case for offloading the construction of the new DataSet object to a new thread (using, say, an AsyncTask) you would have to prove in your benchmark that the overhead from offloading to another thread is small enough to justify your efforts.

Once you have called mChart.setData(newDataSet) then there is very little opportunity for multithreading without making extensive modification to the library. You can see the flow of control for yourself: setData triggers calculation of offsets and preparing matrices. Calculating the offsets is simple floating point addition (inexpensive), the transformation matrices are handled using native C++ code (already optimised).

When the View is drawn, the code in onDraw() must necessarily be executed on the main/UI thread. Although all this may seem sub-optimal, MPAndroidChart still manages to achieve a high level of performance in comparison to other charting libraries as can be seen in this blog post. You can see for yourself what frame rate you are achieving by calling mChart.setLogEnabled(true) and inspecting the logcat for the frame rate.

If you are unsatisfied with the frame rate you are getting, you could consider SciChart which has better performance. Do note, however, that there is a licensing cost for the same. The tl;dr from this is there is only a little space for optimisation in MPAndroidChart as it stands and if performance is a requirement you may have to look to another library.

Community
  • 1
  • 1
David Rawson
  • 20,912
  • 7
  • 88
  • 124
  • Thanks for the insight into the code. As I mentioned, I'm not concerned with recalculating the min and max data values: The axis calibrations are fixed and I don't want to change them. I just want to stuff new data points in there and redraw the line, come what may :-) That being the case I thought that perhaps my 2-line solution would save some time. I'm about to test it. – Robert Lewis May 14 '17 at 20:23
  • @RobertLewis since you won't be needing the recalculation of the min/max perhaps you can fork the library and remove that logic. I imagine you would get a small speedup there. You'd probably have to set the min/max manually yourself via mutators. If you get any results you are welcome to post them as a self-answer, I am sure people will be interested. – David Rawson May 14 '17 at 20:30