Building a live chart (ticker)


#1

I was in the middle of writing a lengthy post asking how to go about writing an updating, scrolling chart like a ticker. It was eaten by the forum, since I apparently was logged out somewhere in between?! Anyway, during the last hour or so I asked all the important questions and went back and forth and finally figured it (almost) out . I won’t let it go to waste and share my findings here and use the opportunity to ask some questions.

First of all, you need a data source that handles all the updating of the chart data (i.e. from an external source, like a stock ticker, or some other measurement). You will have to implement some way to continually retrieve date and when that is done, trigger the chart update. I’ll leave these details out, since everyone wants to do something different here.

Configure your chart like you want. The biggest trouble I had to get this running was to realize, that there seems to be a difference between an axis that is initialized with a fixed and one that is not but gets a range set later on. The former will not reset the axis range after a reloadData: call, while the latter annoyingly does. There doesn’t seem to be a way to make an axis (that wasn’t already initialized with a range) sit still and just change its range like you want it to. It would always go ahead and calculate its range to fit all the data inside. Q: What am I missing here?

When your data updated, you can use reloadData:  to let ShinobiChart know about it, and then set the new range that will scroll the new data into view. 

[chart reloadData];
[self.chart.xAxis setRangeWithMinimum:fiveMinEarlier andMaximum:lastDate withAnimation:YES];

Like this. I have found that the animation gets choppy or downright skipped when you call reloadData:  like this, at essentially the same time as starting the animation. It’s a bit better when you offload the reloading into another thread using dispatch_async(); but still nowhere near smooth as butter. Without reloading the data, everything is smooth, but pointless :wink:     Q: Why is that? Does reloadData somehow block the main thread? Is it the OpenGL rendering? If so, how could I smoothly scroll the chart while allowing for reloading (some of its) data?

Maybe related Q:  What does axisPanningChanged: do?

Thanks, hopefully, this will help somebody and hopefully I can have some answers as well :slight_smile:


#2

Hi, 

this is very interesting.  If you ever create a blog post on the real-time implementation charts using Shinobi please let me know.

I’m in the middle of a migration (from web-based charting to native) and real-time charts is a key feature of my app.  I’ve got a hint on how to deal with it on a reply of my post but since you’re really advanced on your implementation maybe it is not that useful.

Anyway, I hope you get your answers and thanks for sharing your experience.  I’ll definitely have it under consideration when I get to that part of my migration.

Best regards,

Juan. 


#3

Hi Pit,

Sorry you had troubles with the axis auto-ranging - I notice you’re setting the range straight after reloadData, which causes choppyness. I’d suggest changing this to setting/restoring the range in the chart’s renderFinished delegate method, as reloadData doesn’t actually do anything except set a flag (which is why the range may appear to be skipped). The chart’s data loading routing currently uses the main thread to render OpenGL, however we find smoothly scrolling is usually do-able, with average sized amounts of data.

We’re looking at making data-streaming more intuitive, and possibly allowing datapoints to be added without needing an entire OpenGL redraw. Any feedback on this is greatly appreciated :slight_smile:

Kind regards,

Rob


#4

Hello Rob,

I tried that as well, and unfortunately this isn’t the solution. Setting the range in renderFinished:  will send the app in an infinite loop, since changing the range will also trigger the renderFinished: delegate method.

Otherwise you’re right that reloadData: itself doesn’t do anything, which also stopped the chart from updating at all (since the setRangeWithMinimum:andMaximum:withAnimation:method triggered the reload before).

I’d be very glad if you could look into this again, and I’m also looking forward to a better solution to let the ShinobiChart know about just the added datapoints in the future :slight_smile:

All the best!


#5

Hi Pit,

Sorry about that! I neglected to mention you’d need some form of guard in your renderFinished: method. Regardless, we’ve looked into this further and have a blog post that may be of interest to you, about reloading after polling an extra data provider - http://www.shinobicontrols.com/blog/posts/2013/02/12/plotting-live-data-with-shinobicharts/. Feel free to download the code and let me know if this helps you out :slight_smile:

Kind regards!

Rob


#6

I’m using my old code again these days, and while it works fine at first, things really slow down over time. This is because the chart asks for ALL the data points when it reloads its data. And I’m reloading the data 10 times 30 times a second (because thats a nice framerate to keep things scrolling smoothly) to update the chart with new data and scroll it a bit. It really seems like overkill, because usually there are just a few (or even just one) new data points.

So, if ShinobiCharts were to only ask for the data it actually needs, things would be much smoother, I suppose. It should at least not degrade performance over time. More like a UITableView that just keeps asking for more data when the user is about to scroll new cells into view.


#7

Ok, I eliminated the data loading in my delegate methods. Now it’s all down to [ShinobiChart layoutSubviews]  and [ShinobiCanvas layoutSubviews].

I also switched to setting the new x-range without animation (plus triggering -redrawChart), but the layouting still happens. I noticed, that the layoutSubviews calls use exponentially more time the more I let the chart ticker run.

So, what triggers the layout? Or is it the drawing of the axis ticks?

See these snapshots: http://d.pr/i/QmIH   http://d.pr/i/lcZb