Comparison of the Available iOS Charting Libraries

In this post, I explore four of the available iOS charting libraries and investigate the features, API, and pros and cons of each. In order to conduct a fair investigation, I have developed a comparison framework which loads historic stock market data from the Yahoo Finance API. My findings were that Core Plot had a strong set of documentation, yet was a little light on features and built in functionality. Shinobi Charts had super fast performance, was extremely easy to integrate and had a clean, friendly API. The iOS:Chart library from Three D Graphics had a large number of chart types, but had a very confusing API and poor documentation. KeepEdge was difficult to install with another poor API, and also threw unhelpful exceptions when adding data. I have documented my setup and usage experiences of the four libraries below.

The Framework

The framework can be easily extended to display the data fetched in different view controllers that can be set up to use various charting libraries. I start with a discussion around the framework including its architecture, the components it uses and how it operates, then describe the process of integrating each chart type. Finally I come up with conclusions based on my experience. The code used to build the framework and create the comparison project is available for download near the end of this post.

Framework Library / API Usage

Rather than generating random data or using the same hard-coded data every time the comparison was run, stock market data is fetched from the Yahoo Finance API in CSV format . Unfortunately, Yahoo Finance does not provide functionality in its API to find a list of the stocks available. Therefore, a request for a list of the major-cap stocks from the NASDAQ website is created. This again returns results in CSV format. To parse the CSV files returned by each feed, the framework includes the CHCSV Parser created by Dave Delong.

Framework Structure

Stock Selection View in the application.

The framework is based around a Data Transfer Object (DTO) named StockPriceDataContainer. This object contains three properties. The first, StockData, is a StockData object containing metadata about a certain stock. The second property is StartDate, which is the date that we want to get prices from. The third property is the PriceDataArray, which is a NSMutableArray. This array contains PriceData objects, which holds price information for each point in time. PriceData has the properties Date, Index, Open, High, Low, Close, Volume, AdjClose.

The Data Manager has two operations which fetch data to display in the framework. The first, getStockList, uses the company list from the NASDAQ website mentioned previously. The second, getDataForStock, retrieves data in a similar way, but uses a Yahoo Finance query based on a stock symbol and a start date. The result is then converted into an array of PriceData objects, ready to be used by the different charts.

The initial view presented when the framework launches is a UITableView subclass named StockSymbolSelectionTableViewController. The class loads a list of stocks on a new thread when the using the DataManager. As the stock list returned is a large list, the StockSymbolSelectionTableViewController has a search bar to provide search functionality. Once the user has found the stock they would like to view and selected the row the stock is at, a StartDateSelectorViewController is created, passing the StockPriceDataContainer it has just created into the selector's constructor. The StartDateSelectorViewController is a UIViewController subclass that simply displays a date picker and a "Next" button. Once the button is touched a new ChartTypeSelectionTableViewController is created. The StockPriceDataContainer is passed into this new class' init method, and it is then pushed to the navigationController's stack.

Stock Selection View

The ChartTypeSelectionTableViewController class is a UITableViewController subclass that presents a list of the available chart types. The controller again takes a StockPriceDataContainer in its init method. At the end of the init method, the controller will try to load data for this stock using the data manager's getDataForStock: fromStartDate method. It displays a loading indicator in the table's header until the data either loads successfully or fails to load. The chart types are specified manually inside of the cellForRowAtIndexPath:(NSIndexPath*)indexPath method, and in the didSelectRowAtIndexPath:(NSIndexPath*)indexPath method, the controller loads a new ChartBaseViewController subclass based on the chart type that was selected.

When the didSelectRowAtIndexPath method is called, we have all the information we need to display the stock data on a particular chart type. In order to keep the comparison fair, the ChartBaseViewController base class is present to ensure that there is as much common code as possible between the view controllers for each chart type, removing possible bias due to the way that each view controller is created and initialised. This also makes it very simple to add a new library or chart type to the framework, as you just need to create a new subclass and add that to the project.

The ChartBaseViewController is a simple subclass of UIViewController that accepts a stock price data container and stores that container in a field in its init method:

-(id)initWithPriceContainer:(StockPriceDataContainer*)priceContainer;

It also contains code to set the title based on the priceContainer object's StockData property when the view loads, as well as releasing the container in its dealloc method.

The Libraries

Above, I have described how the framework operates and loads data to present in the different charting libraries. I will now describe how to add four different libraries to this framework. The charts have been added following the same patterns as outlined in their respective sample projects. For all of the libraries, a standard line chart has been created with as much of the default behaviour possible to ensure a fair comparison.

Core Plot

Core Plot is a free, open source charting library available under the New BSD License. The library is also available for use in Mac OS X applications. At the time of writing, the current version is 1.0. Core Plot has a wiki, and the new 1.0 version comes with a comprehensive set of API documentation. I found that the source code itself was the best place to look for documentation, as the majority of classes and methods have clear, meaningful comments.

Integrating Core Plot into the framework

In the CorePlotViewController, we first must create a CPTGraphHostingView. Within this view, we create a CorePlotLineChart with the price container, then initialise the plot. The majority of the setup for core plot is done in the CorePlotLineChart class, which implements the CPPlotDataSource protocol. This provides the chart with the data points it must display and is done by implementing the following method:

-(NSNumber*)numberForPlot:(CPTPlot*)plot field:(NSUInteger)fieldEnum recordIndex:(NSUInteger)index.

Core Plot does not have a built in date/time axis type, so the data points in the example have simply been plotted on the x-axis based on their index. The index has to be reversed to make the points appear in the correct order, which is done by setting the x value to the number of data points in the PriceDataArray minus the current index. There are methods to plot date/time points on a Core Plot scatter chart, such as this one documented at Stack Overflow. Core Plot also comes packaged with a DatePlot program in the examples folder, again illustrating that it is possible, but this was beyond the scope of this comparison. The code to do this is not overly excessive, but I would have preferred to have had an inbuilt date time axis that could have computed the correct position of the points based on their date.

To add points to the core plot data source, it is a case of using CGPointMake to combine the Close value of the data point at the current index with the current index itself. Then, based on the fieldEnum value, we return the x or y value of the point.

PriceData *value = [[dataForPlot PriceDataArray] objectAtIndex:index];
CGPoint point = CGPointMake([[dataForPlot PriceDataArray] count] - [value Index], [value Close]);

if (fieldEnum == CPTScatterPlotFieldX)
    return [NSNumber numberWithFloat:point.x];
else
    return [NSNumber numberWithFloat:point.y];

The -(NSUInteger)numberOfRecordsForPlot:(CPTPlot*)plot method must also be implemented to return the number of data points that will be displayed on the chart. The -(void)initialisePlot method sets up the chart itself, assigns the hostedView's hosted graph, and then creates a CPTScatterPlot with styles and properties which it then adds to the chart. We must also set up the plot space, allow user interaction and then set up the interval lengths and tick properties for each axis. The chart is now ready to be given data which it will display.

Core Plot

Shinobi Charts

Shinobi Controls recently released their new charting library, Shinobi Charts. This is a paid library but with a free 30 day trial available. It features several chart types, from Pie and Donut to Bar and Column and Area charts, with more coming soon. The framework also offers Number, Date-Time and Category axis types. Shinobi comes packaged with a comprehensive set of apple style documentation. Although the library is new, the packaged example projects are well put together and are more than enough to illustrate how to include the framework in your code.

Integrating Shinobi Charts into the framework

Adding the Shinobi Chart to the framework is a simple matter of linking the ChartComparison binary with the Shinobi and Security Libraries included in the trial. The setup process is outlined in the Quick Start Guide, which I found extremely useful. For a Shinobi Chart, we again have two classes. The first, ShinobiViewController is another subclass of ChartBaseViewController, which in the viewDidLoad method, creates a ShinobiChart instance. It then sets some properties on that chart to enable interaction before initialising the second class (a ShinobiDataSource). Finally, we add the finished chart to the UIViewController's view.

The ShinobiDataSource class implements four simple methods from the SChartDatasource delegate to provide the chart with data. The first method seriesAtIndex:(int)index is responsible for creating a SChartSeries. In our case, we simple create a SChartLineSeries and return it. The numberOfSeriesInSChart: method just returns the number of series required on the chart. We simply return 1. The numberOfDataPointsForSeriesAtIndex: method returns the number of data points in the price container. The last of the four methods in the SChartDatasource we must implement is dataPointAtIndex: forSeriesAtIndex:. This is where we return a data point for a particular series and data index. This is a matter of creating a SChartDataPoint object with the date and close value of the corresponding data point in the price container. The following illustrates the resulting code:

-(id)sChart:(ShinobiChart*)chart dataPointAtIndex:(int)dataIndex forSeriesAtIndex:(int)seriesIndex
{
    SChartDataPoint *datapoint = [[SChartDataPoint alloc] init];
    datapoint.xValue = [[Data objectAtIndex:dataIndex] Date];
    datapoint.yValue = [NSNumber numberWithDouble:[[Data objectAtIndex:dataIndex] Close]];
    return [datapoint autorelease];
}

Once we have implemented these methods and set all of the chart properties on the chart in ShinobiViewController, the chart can be loaded and is displayed when the view controller loads.

Shinobi Chart View

iOS:Chart

iOS:Chart is a library from Three D Graphics. This is another paid library with a trial available. The library comes with a lot of different chart types and it can also draw 3D charts. There is API documentation available, but as mentioned below, the trial version seems to have different/missing methods to the API, which is not made clear anywhere as far as I could see.

Integrating iOS:Charts into the framework

For iOS:Charts there are two classes which control the chart. The IOSChartView class is a UIView subclass which draws the chart that is set in its setChart: method. It also resizes the chart's backgroundFrameSize property when the updateFrame method is called. This is rather low level behaviour which was abstracted from the user by the other libraries. It is unfortunate that this behaviour must be created by users of the library.

The IOSChartViewController which is a UIViewController creates the chart in its -(void)loadView method and sets various properties. It then sets the data on the chart's data manager, and update's the chart's view and frame. The controller also updates the chart's view and frame when the -(void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation method is fired.

One thing I found confusing when integrating the library into the framework was that sample data was added to the chart even though I did not request any such data be added. The naming of the method -(BOOL)setYPoint:(uint)series group:(uint)group data:(float)y of TDGDataProtocol also made things unclear to me. The notion of a group seemed strange, when setting a y-value on a series, and after reading the documentation it was still not clear what this meant. After using a trial and error approach to try to discover what this group field actually did, it turned out that the it is actually the index of the data point. I have since found a similar method in the API, but there is no such method in the trial. Whether the API in the trial is different to that of the full version is unclear, but I would have liked this to have been specified somewhere if this is the case. The resulting code uses this method to add data to the chart is as follows:

-(void)setData
{
id data = chartView.chart.dataManager;

chartView.chart.seriesCount = 1;
chartView.chart.groupCount = [[_priceContainer PriceDataArray] count];
[[[chartView chart]seriesAtIndex:0] setFormat:1];

for(PriceData *pd in [_priceContainer PriceDataArray])
    [data setYPoint:0 group:[[_priceContainer PriceDataArray] count]-[pd Index] data:[pd Close]];

}

iOS Chart View

KeepEdge

The KeepEdge iPhone Charting Library for iPhone Objective-C is a paid library with a free trial available. It features a wide range of chart types, ranging from a standard line chart to Stock High Low charts and 3D Clustered Bar charts. KeepEdge does include a TimeSeries chart, with a date/time axis.

Integrating KeepEdge Charts into the framework

Importing the KeepEdge and required libraries into the framework is somewhat more complicated than doing so with the other libraries. After following the installation and setup guide, I was still unable to get the charts to run on the simulator and on my iOS devices. It was unclear whether I needed the libKeepedgeChartingIPhone.a from the Release-iphoneos folder or the Release-iphonesimulator. Eventually, I found that the file inside Release-iphonesimulator works on the device too, so that is the file that I chose to use.

After looking at the time series chart documentation, I created the KeepEdgeViewController class. This class simply creates a KeepEdgeTimeSeriesView class, and passes it the PriceContainer. The KeepEdgeTimeSeriesView is a UIView subclass that overrides its drawRect method. In this method, it creates a TimeSeriesChart class, passing in the imageArea and again, the price container. The vast majority of the setup and data management for the KeepEdge chart takes place in this TimeSeriesChart class. The static method processDemoWithContext does the initial setup. We create a IPCTimeSeriesChart, which we set properties on using methods such as getTimeDomainAxis, getRangeAxis and getRender. These method names provided in the demo were not 100% clear to me, and this unfortunately meant that I had to take a trial an error approach to modifying the charts. The next stage of the chart initialisation was adding the data.

The biggest hurdle I found when implementing the KeepEdge chart was when it came to adding the data points. The KeepEdge chart kept throwing an exception whenever I tried to add a certain data point from the data fetched by the framework. I was unable to discover why this was the case, but the exception thrown was unhelpful and there was no information to help describe what was happening. I believe this is a bug with the KeepEdge library, but feel free to contact me if you know otherwise or have any suggestions about what to do to fix this. This bug resulted in the rather messy following code for the TimeSeriesChart class.

for (PriceData *pd in [data PriceDataArray])
{
	@try
	{
		theDate = [pd Date];
		DTCDay *pDay = [[DTCDay alloc] initWithDate:theDate];
		[t1 addWithPeriod:pDay doubleValue:[pd Close]];
	}
	@catch (NSException *e)
	{
		NSLog(@"This date: %@, could not be added. Skipping.", theDate);
	}
}

As you can see above, I have had to add a try catch block around the code that adds the data to the chart. This unfortunately means that a lot of data points will not be added to the chart. Finally, we call drawChartWithContext to draw the chart in the view.

KeepEdge Chart Screenshot

Conclusions

The documentation available in the four different libraries differs greatly. Core Plot has well commented code and there are a lot of different tutorials, guides and forum posts all over the internet. The website also provides a complete API. Shinobi has a great set of apple style documentation and a simple, easy to use API. I personally found the method names of the iOS:Chart library confusing and the documentation on their website was poor. The KeepEdge library is well documented in places - and has specific instructions for each chart type. The strange exception thrown when adding a specific set of data points made it difficult to carry out a comparison for this library however. The lack of detail or information when the exception occurred was not helpful, so unfortunately I have been unable to find a solution to this problem.

When plotting Time Series data like the data included in the framework set up for this post, having a library with a Date/Time Axis is extremely useful. Shinboi includes a Date/Time axis with a great API. Setting the a data point's date time value is a simple matter of assigning its xValue property an NSDate object, i.e. datapoint.xValue = [[Data objectAtIndex:dataIndex] Date]. The KeepEdge Date/Time axis API is a more convoluted, with the following code required to add a data point to the series at a given date (theDate):

DTCDay *pDay = [[DTCDay alloc] initWithDate:theDate];
[t1 addWithPeriod:pDay doubleValue:[pd Close]]

Core Plot does not come packaged with a date/time axis type, but as mentioned above it is possible to implement your own. Looking through the iOS:Charts documentation, I could not find any mention of a Date/Time axis, and found no information online about how to add one.

Zoom and pan behaviours come by default in Core Plot and Shinobi, and simply require turning on. The default zoom behaviour in Shinobi is somewhat more intuitive and advanced however, as the zoom on each axis is independent. When zooming in Core Plot, you can not zoom the x-axis without the y-axis zooming by the same factor at the same time. Shinobi also contains inbuilt behaviours for pan and zoom with momentum, double-tap zoom, double-tap to reset, crosshair, and box gesture zoom. Enabling these behaviours is a simple case of setting a flag on the SChartDateTimeAxis or SChartNumberAxis, which in our case is contained in the ShinobiViewController class. You can see the different behaviours in the large data set sample application that is packaged with the trial version of Shinobi.

In terms of the smoothness of the interaction with the charts, Shinobi has a clear advantage. Pan and zoom have momentum and smoothly move to the new range requested. The performance of the Shinobi interaction is also markedly better than Core Plot, especially when there are a lot of labels visible on the chart. Shinobi zoom and pan limits the visible range to where the chart has data, meaning that you can not zoom out and pan to a region without data - making it hard for the user to get lost and confused when interacting with the chart. It certainly would be possible to add zoom and pan behaviours to the other two frameworks using gesture recognisers and possibly even improve the Core Plot interaction, but that was beyond the scope of this article.

Source

You can download the full source code of the comparison here: ChartComparison.zip

Framework Installation

To build the examples you need to download the charting components from their respective web pages. Due to licensing restrictions I was unable to include them in the download.

Once these frameworks have been downloaded, you must add them to the project using the steps below.

Core Plot

  • Download the Core Plot 1.0 framework from the Core Plot Google Code Website.
  • Unzip the file.
  • Copy the CorePlotHeaders (found in CorePlot_1.0/Binaries/iOS/CorePlotHeaders) to the project's "Frameworks/CorePlot/Headers" group.
  • Copy libCorePlotCocoaTouch.a to the project's "Frameworks/CorePlot" group.
  • Copy the CorePlotHeaders to your Xcode project
  • Make sure that the following text is present in the "Other Linker Flags" in the target build settings: "-ObjC -all_load"
  • Add the QuartzCore framework to the project.

Shinobi

  • Download the Shinobi Charts framework from the Shinobi iOS Charts Website
  • Add references the following frameworks: ShinobiCharts.frameworkSecurity.framework (if you are using the trial version), QuartzCore.framework (should have been added already for Core Plot) and OpenGLES.framework.
  • When you download the trial, you will receive a license key. You will need replace the licenseKeyString in ShinobiViewController with your license key.

iOS:Chart

  • Download the iOS:Chart framework from the Three D Graphics Website
  • Add the iOS:Chart library from the download "lib/libtdgchart.a" to the project.
  • In project build settings, find the search path section and set header search paths to the "include" folder of the iOS:Chart download.
  • Then set library search paths to the "lib" the folder of the iOS:Chart download.
  • Click the project target, and set the header and library search paths to "$(inherited)" so they inhert the settings from the project settings.
  • Add the CoreText framework to the project.

KeepEdge

  • Download the iPhone Charting Library from the KeepEdge Website
  • Find the Release-iphonesimulator folder, and add the libKeepedgeChartingIPhone.a file to the project.
  • In the "usr\local\include" subfolder of Release-iphonesimulator folder, you will find all of the header files. Add these header files to the project in the "Frameworks/KeepEdge/Headers" group.

This comparison has looked at four of the main charting libraries available for iOS developers, comparing the features of each. If you have noticed any errors or possible issues with the source code or any of my assumptions within this article, please feel free to contact me at cgrant@scottlogic.co.uk and I will correct these as soon as possible. In addition, if you are aware of other iOS charting libraries that have not been included in this article, please let me know and I will do my best to update and include them as soon as possible.

Disclaimer: Scott Logic is the parent company for Shinobi controls, but I have evaluated each of the frameworks based on their API and features alone.

blog comments powered by Disqus