Advertisement

Working with CorePlot: Plot Fundamentals

by

When working with data intensive applications, a developer must often do more than just show lists of data records in a table view. The CorePlot library will allow you to add stunning data visualizations to your applications. Find out how in this Tuts+ Premium series!


Also available in this series:

  1. Working With CorePlot: Project Setup
  2. Working with CorePlot: Plot Fundamentals
  3. Working with CorePlot: Styling and Adding Plots
  4. Working with CorePlot: Creating a Bar Chart
  5. Working with CorePlot: Creating a Pie Chart

Where We Left Off

Last time we introduced the CorePlot framework and discussed what it can do and how we can use it to enhance data visualisation in our applications. We also explored the sample application we're going to create in the series and how to add CorePlot to our Application. For a code snapshot of where we left off last time, download the source code for this tutorial (otherwise feel free to use your existing code base and save yourself the download time!).


Step 1. Setting Things Up

Before we can create a graph we'll need a view to do it in. We're going to allow the user to click on a UIBarButtonItem in the 'Students' tab that will bring up an action sheet containing a list of graphs for the user to choose. Once a selection is made a modal view will display a graph with the relevant data on it.

We're going to create a new group called 'Graphing' underneath the 'StudentTracker' group. Create a 'Views' and 'Controllers' group underneath this like in the 'Student Listing' and 'Subject Listing'.

Create a new class called 'STLineGraphViewController' (subclassing UIViewController) in the 'View Controllers' group. When choosing where to add the files the best place to put them is the 'Classes/Graphing/View Controllers' folder (You will have to create the 'Graphing/View Controllers' directory).


We are going to come back and work on customizing this later. Right now we're going to implement that code that allows the user to select a graph to look at.

First open up STStudentListViewController.h and add the 'UIActionSheetDelegate' and 'STLineGraphViewControllerDelegate' protocol declarations. This protocol doesn't exist yet but we will create it later (Also make sure you import the 'STLineGraphViewController.h' file).

 
@interface STStudentListViewController : UIViewController <UITableViewDelegate, UITableViewDataSource, AddStudentViewControllerDelegate, UIActionSheetDelegate, STLineGraphViewControllerDelegate>

Next, open the .m file and implement the 'actionSheet: clickedButtonAtIndex:' method with the following code:

 
- (void)actionSheet:(UIActionSheet *)actionSheet clickedButtonAtIndex:(NSInteger)buttonIndex 
{ 
    if (buttonIndex == 0)  
    { 
        STLineGraphViewController *lineGraphVC = [[STLineGraphViewController alloc] init]; 
        [lineGraphVC setModalTransitionStyle:UIModalTransitionStyleFlipHorizontal]; 
        [lineGraphVC setDelegate:self]; 
        [lineGraphVC setManagedObjectContext:[self managedObjectContext]]; 
         
        [self presentModalViewController:lineGraphVC animated:YES]; 
        [lineGraphVC release]; 
    } 
}

This code shouldn't need too much explaining. We are simply creating a LineGraph View Controller and presenting it modally. We set ourselves as the delegate so that we know when to dismiss the modal view. We also give the view controller a managed object context to work with so it can interface with core data. These last two methods will create a warning (or error if using ARC) because the properties don't exist yet, but we will be creating them later.

Next we'll create a method to call the action sheet and add a UITabBarItem to call it from. Add a method declaration in the .m interface called 'graphButtonWasSelected:':

 
@interface STStudentListViewController () 
@property (nonatomic, strong) NSArray *studentArray; 
 
- (void)addStudent:(id)sender; 
- (void)graphButtonWasSelected:(id)sender; 
@end

Next, add the method implementation:

 
- (void)graphButtonWasSelected:(id)sender 
{ 
UIActionSheet *graphSelectionActionSheet = [[[UIActionSheet alloc] initWithTitle:@"Choose a graph" delegate:self cancelButtonTitle:@"Cancel" destructiveButtonTitle:nil otherButtonTitles:@"Enrolment over time", nil] autorelease]; 
     
	[graphSelectionActionSheet showInView:[[UIApplication sharedApplication] keyWindow]]; 
}

Next we need to add a UIBarButtonItem for the user to select when they want to view the graph. We'll do this in the viewDidLoad method underneath where we create the right bar button item:

 
[[self navigationItem] setLeftBarButtonItem:[[[UIBarButtonItem alloc] initWithTitle:@"Graphs" style:UIBarButtonItemStylePlain target:self action:@selector(graphButtonWasSelected:)] autorelease] animated:NO];

Finally, we need to implement a STLineGraphViewController protocol method that will tell the controller to dismiss the modal view when the user is done looking at the graph:

 
- (void)doneButtonWasTapped:(id)sender 
{ 
    [self dismissModalViewControllerAnimated:YES]; 
}

Now we're ready to start creating the graph!


Step 2. Set Up the Graph Hosting View

First we need to create a custom view class for our LineGraphViewController. In the Graphing>Views group create a new class that extends UIView called 'STLineGraphView' (Make sure to put it in the correct folder when choosing where its saved on the file system).


We're going to set the graphical aspects of the view up in the view class. First go to the .h file and (after importing the "CorePlot-CocoaTouch.h" file) and add the following property declaration:

 
@property (nonatomic, strong) CPTGraphHostingView *chartHostingView;

The CPTGraphHostingView is simply a UIView that is responsible for containing the graph and allowing user interaction (which we cover in a later tutorial).

Synthesize the chartHostingView and create the chart hosting view in the initWithFrame method:

 
[self setChartHostingView:[[[CPTGraphHostingView alloc] initWithFrame:CGRectZero] autorelease]]; 
[self addSubview:chartHostingView];

The above should be fairly self explanatory. We create a CPTGraphHostingView and set it as our chartHostingVIew property. We then add it as a subview.

Next we need to set the graphViews frame dimensions in the 'layout subviews' method:

 
[super layoutSubviews]; 
     
float chartHeight = self.frame.size.height - 40; 
float chartWidth = self.frame.size.width;       
     
[[self chartHostingView] setFrame:CGRectMake(0, 0, chartWidth, chartHeight)]; 
[[self chartHostingView] setCenter:[self center]];

Again, all of the above should be simple stuff. Before we start working on the controller, make sure you release the 'chartHostingView' property in the views dealloc method if you haven't already.


Step 3. Creating the Line Graph

Most of the work we're going to do now is going to be in the controller. Open STLineGraphViewController.h and add the following property declarations:

 
@interface STLineGraphViewController : UIViewController <CPTScatterPlotDataSource, CPTScatterPlotDelegate> 
 
@property (nonatomic, strong) CPTGraph *graph; 
@property (nonatomic, assign) id<STLineGraphViewControllerDelegate> delegate; 
@property (nonatomic, strong) NSManagedObjectContext *managedObjectContext;

The CPTGraph is an abstract class that is responsible for drawing the graph elements and managing the different plots. It is also responsible for applying themes, reloading the graph data, and much more! We're also indicating that we will comply with the CPTScatterPlotDataSource and CPTScatterPlotDelegate protocols.

We also need to add a protocol of our own so that the modal view can be dismissed. Put the following code above the interface declaration:

 
@protocol STLineGraphViewControllerDelegate 
@required 
- (void)doneButtonWasTapped:(id)sender; 
 
@end

Switch over to the *.m file and synthesize the graph and delegate properties. Once this is done, add the following code before the 'viewDidLoad' method:

 
[self setView:[[[STLineGraphView alloc] initWithFrame:self.view.frame] autorelease]]; 
     
CPTTheme *defaultTheme = [CPTTheme themeNamed:kCPTPlainWhiteTheme]; 
     
[self setGraph:(CPTGraph *)[defaultTheme newGraph]]; 
     
[defaultTheme release];

There are a few things going on in this section. First, we are creating and setting the view of the controller as our custom STLineGraphView. Next, we create a 'CPTTheme' object. The CPTTheme object manages the style of a graph with line styles, text style, and any fills that are required. An easy way to get a CPTGraph preconfigured with a base CPTTheme is to create the CPTTheme with one of the predefined theme names and then using the method 'newGraph' to give us a graph styled with it.

Next, we're going to put the following code into the 'viewDidLoad' Method:

 
[super viewDidLoad]; 
STLineGraphView *graphView = (STLineGraphView *)[self view]; 
[[self graph] setDelegate:self]; 
     
[[graphView chartHostingView] setHostedGraph:[self graph]]; 
     
CPTScatterPlot *studentScatterPlot = [[CPTScatterPlot alloc] initWithFrame:[graph bounds]]; 
[studentScatterPlot setIdentifier:@"studentEnrollment"]; 
[studentScatterPlot setDelegate:self]; 
[studentScatterPlot setDataSource:self]; 
     
[[self graph] addPlot:studentScatterPlot]; 
 
UINavigationItem *navigationItem = [[UINavigationItem alloc] initWithTitle:self.title]; 
    [navigationItem setHidesBackButton:YES]; 
     
UINavigationBar *navigationBar = [[[UINavigationBar alloc] initWithFrame:CGRectMake(0, 0, self.view.frame.size.width, 44.0f)] autorelease]; 
[navigationBar pushNavigationItem:navigationItem animated:NO]; 
     
[self.view addSubview:navigationBar]; 
     
[navigationItem setRightBarButtonItem:[[[UIBarButtonItem alloc] initWithTitle:@"Done" style:UIBarButtonItemStyleDone target:[self delegate]  
                                                                           action:@selector(doneButtonWasTapped:)] autorelease] animated:NO];

In the above code we are getting our view and setting the hosted graph for the views chart hosting view to our graph object. We then create a 'plot' to put on the graph. We use the 'CPTScatterPlot' when we want to create a line graph. The identifier is something we can use to identify the plot later on. We then set the delegate and datasource to the controller class as it will be responsible for providing the data for the graph. Finally, we add the newly created plot of the graph.

After we work with the graph, we create a navigation item and bar for the modal view controller to display a title and a done button that will take them back to the original view.

Try running the project now and going to the line graph. You should see something like the following:


We have the start of a graph! Now to add some data:


Step 4. Adding Data to the Graph

Plots in CorePlot use two main datasource methods for obtaining data, 'numberOfRecordsForPlot' and 'numberForPlot:field:recordIndex:'. It is very similar to how table views work. First, we want to specify the number of records for the plot:

 
- (NSUInteger)numberOfRecordsForPlot:(CPTPlot *)plot 
{ 
	return 8; 
}

We are showing how many enrollments occurred on every day of the week. Since there are 7 possible days where student could have enrolled, we have 8 total records (because we start at 0).

Now we want to specify what the x and y value should be for each record:

 
- (NSNumber *)numberForPlot:(CPTPlot *)plot field:(NSUInteger)fieldEnum recordIndex:(NSUInteger)index 
{ 
    NSUInteger x = index; 
    NSUInteger y = 0; 
     
    NSError *error; 
     
    NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init]; 
    NSEntityDescription *entity = [NSEntityDescription entityForName:@"STStudent" inManagedObjectContext:managedObjectContext]; 
    NSPredicate *predicate = [NSPredicate predicateWithFormat:@"dayEnrolled == %d", index]; 
     
    [fetchRequest setEntity:entity]; 
    [fetchRequest setPredicate:predicate]; 
     
    y = [managedObjectContext countForFetchRequest:fetchRequest error:&error]; 
     
    [fetchRequest release]; 
     
     
    switch (fieldEnum)  
    { 
        case CPTScatterPlotFieldX: 
            NSLog(@"x value for %d is %d", index, x); 
            return [NSNumber numberWithInt:x]; 
            break; 
        case CPTScatterPlotFieldY: 
            NSLog(@"y value for %d is %d", index, y);             
            return [NSNumber numberWithInt:y]; 
            break; 
             
        default: 
            break; 
    } 
     
    return nil; 
}

There is a fair bit going on above. This method needs to specify an x and y value for a given index and which value it returns is based upon the 'fieldEnum' value (Which in our case is either CPTScatterPlotFieldX or CPTScatterPlotFieldY). The index indicates the record it's about to plot onto the graph and the plot refers to the actual plot that is displaying the data. When we have a controller managing more than 1 plot, we can look at the plot identifier to determine which set of data to provide (we cover this process in a later tutorial).

I find it easiest to specify an 'x' and 'y' value at the beginning of the method, working out both values, and then based on the field enum returning the correct one in the form of an NSNumber (which is the format that corePlot requires it in).

The x value is easy to figure out. As it displays the days of enrollment, x is equal to the current index. The y value is going to be a count of all students enrolled on that day. We can get this by making a call to our core data store and looking for all STStudent records with a 'dayEnrolled' value of the index. If you are unfamiliar with core data and dont understand everything thats going on dont be too concerned, for now it's ok that it works. Focus on learning one thing at a time!

If you save and run your application now, you still won't see anything displayed on the graph, but you should see the following output in your console:


This means that the graph is getting the correct values for x and y (make sure that it is the same or similar to the output in the picture. It's still not showing on the graph however. That's because if you look at the graph, the displayed range is incorrect. We are looking at -1.0 to 0 on both the x and y axis. We need to set the range to look at before we can see the data points.

The plot space determines much about how the graph is viewed and formatted. We will be covering a little bit in this tutorial and will go into much greater detail in the next one.

To set the x and y range that the user looks at we need to work with the 'CPTXYPlotSpace' object. This object allows us to set a viewable range for the graph.

Go to the viewDidLoad method and add the following code right underneath where we add the plot to our graph:

 
CPTXYPlotSpace *studentPlotSpace = (CPTXYPlotSpace *)[graph defaultPlotSpace]; 
[studentPlotSpace setXRange:[CPTPlotRange plotRangeWithLocation:CPTDecimalFromInt(0) length:CPTDecimalFromInt(7)]]; 
[studentPlotSpace setYRange:[CPTPlotRange plotRangeWithLocation:CPTDecimalFromInt(0) length:CPTDecimalFromInt(10)]];

First, we get a CPTXYPlotSpace object from the graphs default plot space (some casting is required). Then we simply set the x and y range. The range is a 'CPTPlotRange' object that we create statically using the 'plotRangeWithLocation:length:' method. This method takes NSDecimals but corePlot provides us with a function we can use to get a decimal from an int called 'CPTDecimalFromInt' (There is also one for float if that is required).

Now save and run the application and you should see the start of your first graph!



Next Time

We have the starting of a graph but it needs a bit more work before it can be very useful. Next time we're going to discuss how to set and format axis labels, tick lines, and increments. We're also going to discuss how to customize the look of the graph, and, finally, how to add and manage multiple plots on a single graph. See you next time!

Advertisement