It’s unfortunate that Apple do not provide a charting library bundled with their frameworks, for I suspect that would save many developers the trouble of finding a charting library and deciphering the documentation. I’ve come to the conclusion that CorePlot is the best bet at the moment - it’s powerful and compatible with both iOS and OSX but unfortunately the documentation for isn’t great. So, we’re going to go through the process of adding a simple scatter chart to an iPad application.
Our goal is to build an application that contains a simple scatter graph with data stored in an NSArray (which could easily have been loaded from a CSV file). We aim to add this in a generic fashion, in a sub-view of a NIB file - so that you could drop this work into an app you’re working on without much work. We target the iPad, but the same method will work for the iPhone without any changes and will also work on OSX if you make a couple of minor changes.
This tutorial assumes that basic iOS programming knowledge and experience. Before we start, it’s worth noting that CorePlot is a very flexible library - it just takes a bit of getting used to. Unfortunately, the base documentation doesn’t help much when you’re getting started - so I’ll try to expose as much as I currently know about the library.
1. Starting a new Project
We’re going to develop a simple single-view iPad application - but you can add your CorePlot to any existing view in an application using the same method presented here. I use a class prefix of TUT to represent any classes I build during this tutorial.
2. Acquiring and Installing CorePlot
When your project is ready and waiting, the first thing we will do is install the CorePlot library, which will expose the classes necessary for you to get started in adding your scatterplot to your application. Head over to the CorePlot website and download the latest zip file. This tutorial is based on CorePlot 0.9, so if the code has changed significantly in the future - you might want to download version 0.9 to work through this tutorial and then figure out how the code has changed later.
When your download is complete, unzip the files in your Downloads directory. We’re going to install the static library, where the code has already been compiled as libCorePlot-CocoaTouch.a, but you could install the source-code version instead if you prefer - which would then recompile CorePlot when you build your app. Both methods are essentially the same, as you’ll still need to include the header files so that your code can access the methods. I keep my external libraries separate from my own source code, so we’re going to add a new folder at the root of our project called ‘Libs’ and then create a sub-directory inside that called ‘CorePlot’. Now, from the zip file that you downloaded - you want to copy libCorePlot-CocoaTouch.a and the folder CorePlotHeaders from the folder CorePlot_0.9/Binaries/iOS/. Your directory structure should look like this;
Now that your files are in place, you just need to add them to your Xcode project. Drag and drop the whole Libs folder onto your Xcode file tree. Or, you can use the File -> Add Files to “Core Plot Introduction” option from the files menu and select the Libs folder. There should now be a Libs folder in your project sidebar containing the CorePlotHeaders and the libCorePlot file. Now, there are a couple of settings we need to tweak to get CorePlot working before we can start to implement our scatter plot. Select your project from the navigator panel in Xcode and then highlight the Build Settings tab. Filter the settings or locate ‘Other Linker Flags’ from this list and then add -ObjC -all_load to these settings, as shown in the next figure;
There’s just one more step before we can start working with CorePlot in our application. As CorePlot makes use of Apple’s Core Animation code, which is a part of the QuartzCore framework - we need to ensure that our project is linking against the QuartzCore code. With the project still selected in Xcode, highlight the Build Phases tab. Expand the Link Binary with Libraries panel and click on the plus button at the bottom. Now select QuartzCore.framework from the list and hit ok. I usually copy the reference in my project into the Framework folder to be tidy - but this isn’t a necessity.
3. Adding a Graph Hosting View to our Interface
In order to add a CorePlot chart space to our application, we start by setting up a ‘Hosting View’ which we will later attach our graph(s) too. We’re just creating a placeholder in our NIB at the moment, into which we will tell CorePlot to start drawing using our code later on.
We start by adding a label to the top of the interface with some dummy text and change the background colour of the main XIB view to Scroll View Textured Background Color to make it looks swish. Next, drag and drop a UIView component from the object library onto your project. You would normally make this as big as possible, as we would adding padding within CorePlot (to leave spill over space for axis labels). However, for this tutorial - I’ve left a border to make it easier to see what’s going on in the figures. Next, select the view you’ve just added and change it’s class in the property panel to the right. It’s class should be; CPTGraphHostingView. You should now have something that looks like the next figure (notice the object hierarchy on the left and the UIView’s class at the top right).
4. Creating our Scatter Plot object
CorePlot acquires data for its plots much like UITableView does - by calling specific methods on the class you have set as the Graph’s delegate. Incidentally, it also delegates many of it’s features this way too, for instance if you want the user to be able to interact with the chart. So, much like a table view, you could set the main ViewController as your delegate and not add an extra class to your code. However, in order to keep your charts re-usable and interchangeable, I think it’s best to create a new class that will do all the work for your chart.
So, we add a new class to our project TUTSimpleScatterPlot, which is simply a subclass of NSObject. In TUTSimpleScatterPlot.h we need to import the CorePlot libraries; #import “CorePlot-CocoaTouch.h” and then we define a few properties for the objects we need to keep a reference too. The header file for our chart object looks like this…
We need to keep hold of 3 objects to make our chart work. We will pass the hosting view from our XIB file and an array of data when we call initWithHostingView:andData: while creating an instance of our chart. We create the CPTXYGraph and setup our chart when initialisePlot is called - which we will usually call shortly after we initialise our object. Notice we have stated that our object responds to the CPTScatterPlotDataSource delegate methods in the class definition.
Next, we’re going to write the init and initialisePlot methods, as well as the methods from the CPTScatterPlotDataSource protocol required to get our chart working. In time honoured tradition, I’ll throw the code at you first, and then explain it. While it looks like a lot of code, most of it involves configuring certain graph elements - it’s simpler than it first appears.
First of all, lets look at each of the methods above and describe what they do, and then we’ll look at some of the Core Plot objects that we make use of in the initialisePlot method.
So, initWithHostingView:andData simple initialises this object and retains a reference to the hosting view and data array which is fed to this method. initialisePlot is designed to be called shortly after the init method, but has been separated so that you can choose when the brunt of the work is done in creating the graph. This method provides the bulk of this class, which mostly consists of setting up the graph object with the required axis settings, line styles and label styles etc.
numberOfReportsForPlot: is a delegate method which simply returns the number of unique data points that exist for a given plot. If you are using this object as a delegate for multiple plots, you can determine which plot you should return data for by interrogating the identifier property - which you set earlier in the initialisePlot method. Finally, numberForPlot:field:recordIndex: will be called twice per data point in each plot. fieldEnum tells you whether the graph object is expecting the X or Y value for a given data point at index. Like with numberOfReportsForPlot, you can differentiate between multiple plots by first checking the identifier of the plot.
The intialisePlot method is fairly self-explanatory when reading it with the comments. However, it took me a while to get my head around the different objects made available by CorePlot. The CPTXYGraph represents an area into which a 2-dimensional graph will be drawn inside of a HostingView which exists in the parent UIView. The graph comes with it’s own plotAreaFrame (the area inside the graph that the actual data will be rendered), a plotSpace (the minimum and maximum values of X and Y that will be drawn on your chart) and an axisSet (essentially style and rendering options for the axis on this graph). You generally pull out a reference from the graph object for each of these and then manipulate them to design your chart.
The most prominent style and design objects you will use are; CPTMutableLineStyle - which represents a set of options for how a given line should be drawn (width / colour), CPTMutableTextStyle - a set of objects describing how a text label should be drawn (font, size, colour) and CPTPlotSymbol - representing an ellipse or cross for instance that will appear at the actual data points on your chart lines. When all of your chart framework is setup, you can then add one or more CPTScatterPlot objects to your graph - each one representing a unique data set or line on your chart. We only add one in this example and set the delegate to self (as we’ve set the delegate methods which return the data), a line style and a plot symbol. We also set an identifier incase we add more plots in the future so that the delegates can tell which plot the graph is expecting data for when calling a delegate method.
5. Hooking it all together
Last but not least, we need to bring all of these elements together and look at our creation. So, we need to create an IBOutlet for our Graph Hosting View in TUTViewController. Your TUTViewController.h should look like the following;
Now, don’t forget to hook your CPTGraphHostingView from the TUTViewController back to the view that you added to the NIB right back at the start of this tutorial. Load up TUTViewController.xib and Ctrl-Click-Drag from ‘Files Owner’ to the Hosting View and then select graphHostingView as the outlet. We’ll create some sample data and then create the scatterPlot object in the viewWillAppear method of the View Controller;
Note that we’re storing our data as a simple array of CGPoints. However, as CGPoints aren’t descended from NSObject, we can’t store them in an NSArray directly, so we wrap them up in an NSValue object within the array.
Anyway, phew, it feels like a lot of work - but we’re finally there. The golden spot of any tutorial - the screenshot of the final product!
If you’re having any problems getting this to work in your own app, then please don’t hesitate to get in touch by leaving a comment below - I’ll do my best to reply in a timely manner. If you want to download the code from the sample project, you can do so using the link below.
Download Notes: You will need to change the interface definition of the TUTViewController class to the following in order to clear the build warning.