Blog

Back to Blog

Building a Tornado Chart

Posted on 4 Jun 2013 Written by Chris Grant

A Tornado Chart is a special type of bar chart where the bars extend from a defined midpoint – known as the “base” value. With a small amount of customisation and calculation, building a Tornado Chart with ShinobiCharts is simple. This post describes the best way to implement Tornado Charts using Shinobi, and includes a sample project (which you can download here) to get you up and running.

Tornadochart

Let’s start with our only UIViewController class in this project – “TornadoViewController”. We don’t do anything fancy at all in this class. In fact, all of our code is in the init method.

First, we set up a datasource:

_datasource = [TornadoDataSource new];
[_datasource setBaseValue:BASE];

Note that BASE is ‘#define’d as @50 above. You can change this base value at any time – see the bottom of the article for how. The data for the datasource is added in the init method of the TornadoDataSource. You can specify your own data by removing that constructor completely and setting “_datasource.data”. This must be an array filled with “TornadoDataPoint” objects, which is a model object that we will discuss soon.

Moving on, we set up a chart, give it an autoresize mask and set the datasource on the chart. We must keep a reference to the datasource as the chart.datasource property is an assign property, meaning it only stores a weak reference.

We then set the axes on the chart. The X-Axis is a number axis, and the Y-Axis is a category axis. Setting the properties “enableGesturePanning” and “enableGestureZooming” enables gesture panning and zooming, and the properties “enableMomentumPanning” and “enableMomentumZooming” enables momentum panning and zooming. We want the X-Axis to appear at the top of the tornado chart, which is easily done by the following line of code:

[_chart.xAxis setAxisPosition:SChartAxisPositionReverse];

Once our chart has two axes, we need to add an annotation at the “BASE” value to represent the bar that goes through the middle of a tornado chart. Creating a vertical line annotation is simple and then we simply add it to our chart.

_baseAnnotation = [SChartAnnotation verticalLineAtPosition:BASE
                                                         withXAxis:_chart.xAxis
                                                          andYAxis:_chart.yAxis
                                                         withWidth:2.0
                                                         withColor:_chart.plotAreaBorderColor];

The final thing we have to do in our TornadoViewController is set the default range of the chart. This lets us specify the range of data we want to be visible. I’ve chosen 0 to 100 here (50 either side of my base value), but you can use any value you like. 

SChartNumberRange *range = [[SChartNumberRange alloc] initWithMinimum:@0 andMaximum:@100];
[[_chart xAxis] setDefaultRange:range];

Now, let’s have a look at the TornadoDataSource class. This class implements the SChartDatasource protocol. The first thing we do is in the constructor. As I mentioned earlier, this is where we set up dummy data for the datasource to use. You should remove this init method and set your own data using the “data” property on the TornadoDataSource class when it comes to using this in your own application. The data must be of type TornadoDataPoint for this to work. TornadoDataPoint is a simple model object with the following interface:

@property NSNumber *min;
@property NSNumber *max;
@property NSString *category;

-(id)initWithMin:(NSNumber*)min
               max:(NSNumber*)max
andCategoryName:(NSString*)cat;

As you can see, theres nothing complicated here. The min property represents the value you want your bar to start on the chart, and the max represents the value where you want your chart to end. Category name is just the Y-Axis value. 

Once we have created a few dummy TornadoDataPoints, we can continue to implement our datasource. But before we do, let me explain how it is going to work. In order to get our values to center around one point, we need three series. The first series is just padding – it offsets the start point of the second series so that the second series will start somewhere on the left of the base, and then end at precisely at base. The third and final series starts at base, and extends into the right region of our chart. This is illustrated by the picture below:

Screen Shot 2013 05 17 At 09 27 34 

This illustrates what we are aiming for initially.

So, we need 3 series. One for padding, one for the min x value, and one for the max x value. So let’s implement the numberOfSeriesInSChart method and return 3.

Now, we need to actually give our chart those 3 series via the seriesAtIndex method. We simply create a new SChartBarSeries object and make sure that the stack index is set to 1. Setting the stack index to 1 for every series means that they will all stack on top of each other.

SChartBarSeries *barSeries = [SChartBarSeries new];
barSeries.stackIndex = @1;

The only special case we need is for the series at index 0, our “padding” series. Remember the screenshot above? We obviously don’t want the first series to be visible, so lets “hide” this series. Strangely, when I say “hide” I don’t mean hide by setting the hidden property on the series. This would effectively remove the series, meaning that there would be no padding! So we approach it a little differently:

if(index == 0) {
    barSeries.style.lineColor = [UIColor clearColor];
    barSeries.style.showArea = NO;
}

Doing this effectively makes the series invisible, which is what we want. Then, all we do is return barSeries.

Next up is the numberOfDataPointsForSeriesAtIndex method, which is simple. All we do is return the number of elements in the “data” property!

Finally, we need to actually do the calculation to make the bars line up. This is actually really simple, and we do it in the dataPointAtIndex forSeriesAtIndex method. First, we need to find which data point we are looking at.

TornadoDataPoint *tornadoData = [_data objectAtIndex:dataIndex];

Then comes the interesting part – but it’s really not complicated at all! We need cases for each series in our chart, which we base on the seriesIndex that this method gives us. We use the first series as padding, and this is the code:

xVal = tornadoData.min;

Simple. All we do here is set the padding bar to start at the tornadoData object’s min value.

For the second series, (the min value bar):

xVal = [NSNumber numberWithDouble:_base.doubleValue - tornadoData.min.doubleValue];

We subtract the tornadoData object’s minValue from the baseValue. This gives us the correct location of the second bar. Finaly, for the third series, we subtract the base value from the tornadoData object’s max value.

xVal = [NSNumber numberWithDouble:tornadoData.max.doubleValue - _base.doubleValue];

This gives us the correct size for our final bar.

Then we just return the SChartDataPoint we have created from the dataPointAtIndex forSeriesAtIndex method.

This is the result!

Tornadochart

So there we have it! A simple example of a Tornado Chart using ShinobiCharts for iOS. If you do want to use this in your own project, then all you need to do is set the data property of TornadoDataSource to an array of TornadoDataPoint objects.

You will be able to change your base value at any time by calling ‘setBaseValue’ on the datasource. This will require the following code if you wish to recalculate the data and redraw the chart:

[_chart reloadData];
[_chart redrawChart];

You will also have to update the position property of the annotation too, which will be similar to this:

[_baseAnnotation setPosition:newBaseValue];

It is also worth noting that you can invert your bars. It’s perfectly fine to set a minValue which is greater than the maxValue on your chart. This will be reflected by the orange bar being on the left and the blue bar being on the right, as illustrated in the screenshot above. This, for example:

[[TornadoDataPoint alloc] initWithMin:@75 max:@25 andCategoryName:@"Fifth"]

Is perfectly valid and will result in your bar looking like the “Fifth” bar from above.

And that’s it! You can download the source code created as part of this blog post here. You’ll need to add the ShinobiCharts Framework to the project. If you don’t have a copy, download a free trial now!

Back to Blog