Advertisement

Working with CorePlot: Creating a Bar Chart

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 went over how to customize the look and style of a line graph (or scatter plot) using classes such as CPTMutableTextStyle and CPTMutableLineStyle. We learned how to customize the X and Y axis increments and number styles using the CPTXYAxisSet and CPTXYAxis classes. We also looked at how to add multiple plots to your graph and modify the data source methods to provide the correct data for the right plots using plot identifiers.


What We'll Cover Today

Today we'll be working with a completely new graph. We will be creating a bar chart that shows the total number of students in each subject with each bar representing a subject. We'll also look at how to customize the look and feel of the graph. Let's get started!


Step 1: Setting Up

First up we need to add the relevant classes to our project. Let's create a ViewController called 'STBarGraphViewController' and a 'STGraphView'. (Make sure you put them in the relevant groups)


Notice the naming of the view to 'STGraphView' instead of 'STBarGraphView'. Going forward we will be using this view to display the bar and pie graphs.

Before we start working with any code we need to add a button to the Action sheet of our student list view. First open up 'STStudentListViewController.h' and import STBarGraphViewController. Add STBarGraphViewControllerDelegate to the list of registered protocols (the protocol actually doesn't exist yet but we will create it later):

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

Next, jump into the .m file and locate the 'graphButtonWasSelected:' method. Add 'Enrolment by subject' to the list of 'otherButtonTitles:':

 
UIActionSheet *graphSelectionActionSheet = [[[UIActionSheet alloc] initWithTitle:@"Choose a graph" delegate:self cancelButtonTitle:@"Cancel" destructiveButtonTitle:nil otherButtonTitles:@"Enrolment over time", @"Enrolment by subject", nil] autorelease];

Now find the 'actionSheet:clickedButtonAtIndex:' method and modify it to work with buttonIndex == 1:

 
if (buttonIndex == 0)  
{ 
    STLineGraphViewController *graphVC = [[STLineGraphViewController alloc] init]; 
    [graphVC setModalTransitionStyle:UIModalTransitionStyleFlipHorizontal]; 
    [graphVC setModalPresentationStyle:UIModalPresentationFullScreen]; 
    [graphVC setDelegate:self]; 
    [graphVC setManagedObjectContext:[self managedObjectContext]]; 
         
    [self presentModalViewController:graphVC animated:YES]; 
         
    [graphVC release]; 
 
} 
else if (buttonIndex == 1) 
{ 
    STBarGraphViewController *graphVC = [[STBarGraphViewController alloc] init]; 
    [graphVC setModalTransitionStyle:UIModalTransitionStyleFlipHorizontal]; 
    [graphVC setModalPresentationStyle:UIModalPresentationFullScreen]; 
    [graphVC setDelegate:self]; 
    [graphVC setManagedObjectContext:[self managedObjectContext]]; 
         
    [self presentModalViewController:graphVC animated:YES]; 
         
    [graphVC release]; 
}

Again, this will show some warnings because we haven't implemented the delegate or managedObjectContext properties on STBarGraphViewController yet.

Now jump into STBarGraphViewController.h. Import CorePlot-CocoaTouch.h and add the following property declaration:

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

Now add the following properties:

 
@property (nonatomic, strong) CPTGraph *graph; 
@property (nonatomic, assign) id<STBarGraphViewControllerDelegate> delegate; 
@property (nonatomic, strong) NSManagedObjectContext *managedObjectContext;

Finally, register that this class will follow these protocols:

 
@interface STBarGraphViewController : UIViewController <CPTBarPlotDelegate, CPTBarPlotDataSource>

Notice that, aside from conforming to different protocols, this class is the same as STLineViewController. Ideally, you would have a base class that would have these properties that you would subclass to reduce code repetition. This tutorial is focusing on core plot only, so we are only focusing on how best to work with the CorePlot framework. If you've got the knowledge and time, feel free to create the base class and use inheritance, but we're going to keep it simple in the sample code here.

Next, jump into the .m file, synthesize the properties, and release them in the dealloc method.

Now let's link the view class as the controller's view. Import 'STBarGraphView.h', and add the following method:

 
- (void)loadView 
{ 
    [super loadView]; 
     
    [self setTitle:@"Enrolment by subject"]; 
    [self setView:[[[STGraphView alloc] initWithFrame:self.view.frame] autorelease]]; 
     
    CPTTheme *defaultTheme = [CPTTheme themeNamed:kCPTPlainWhiteTheme]; 
     
    [self setGraph:(CPTGraph *)[defaultTheme newGraph]]; 
}

Now we're ready to work with the view. Open up STGraphView.h, import the core plot framework (CorePlot-CocoaTouch.h), and add the following property declaration:

 
@property (nonatomic, strong) CPTGraphHostingView *chartHostingView;

Jump into the .m, synthesize the property, and release the property in the dealloc method. Then create the CPTGraphHostingView in the 'initWithFrame:' method:

 
- (id)initWithFrame:(CGRect)frame 
{ 
    self = [super initWithFrame:frame]; 
    if (self)  
    { 
         
        [self setChartHostingView:[[[CPTGraphHostingView alloc] initWithFrame:CGRectZero] autorelease]]; 
        [chartHostingView setBackgroundColor:[UIColor purpleColor]]; 
         
        [self addSubview:chartHostingView]; 
         
    } 
    return self; 
}

Finally, create the 'layoutSubviews' method with the following code:

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

You'll notice that the code used to create this view is exactly the same as STLineGraphView. We can use this basic view to work with all the graph views going forward.

Jump back into the 'STBarGraphViewController.m' view and locate the 'viewDidLoad' method. First, we want to create a CPTBarPlot and add it to our graph:

 
 
[[graphView chartHostingView] setHostedGraph:[self graph]]; 
 
CPTBarPlot *subjectBarPlot = [[CPTBarPlot alloc] initWithFrame:[graph bounds]]; 
[subjectBarPlot setIdentifier:@"subjectEnrollement"]; 
[subjectBarPlot setDelegate:self]; 
[subjectBarPlot setDataSource:self];   
 
[[self graph] addPlot:subjectBarPlot];

Finally, let's add a navigation bar with a done button, so the user can get back:

 
UINavigationItem *navigationItem = [[[UINavigationItem alloc] initWithTitle:self.title] autorelease]; 
[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];

Now let's start working on the data source methods:


Step 2: Giving the Bar Graph Data

The process is going to be similar to how we created the line graph, but we will be making things a bit more dynamic. We will be creating custom methods that will provide an appropriate plot space. We will also be adding some extra data source methods to provide a specific color for each bar as well as x-axis titles.

Before we look at providing the graph data, we need to set up the axis and visible ranges. To do this we need two methods that calculate the maximum x and maximum y values. Declare the following methods at the top of the .m file:

 
 
[[graphView chartHostingView] setHostedGraph:[self graph]]; 
 
@interface STBarGraphViewController () 
- (float)getTotalSubjects; 
- (float)getMaxEnrolled; 
 
@end

Now implement them as per below:

 
#pragma mark - Private Methods 
- (float)getTotalSubjects 
{ 
    NSError *error = nil; 
    NSFetchRequest *fetchRequest = [[[NSFetchRequest alloc] init] autorelease]; 
    NSEntityDescription *entity = [NSEntityDescription entityForName:@"STSubject" inManagedObjectContext:managedObjectContext]; 
    [fetchRequest setEntity:entity]; 
     
    return [managedObjectContext countForFetchRequest:fetchRequest error:&error]; 
} 
 
- (float)getMaxEnrolled 
{ 
    float maxEnrolled = 0; 
     
    NSError *error = nil; 
     
    for (int i = 0; i < [self getTotalSubjects]; i++)  
    {    
        NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init]; 
        NSEntityDescription *entity = [NSEntityDescription entityForName:@"STStudent" inManagedObjectContext:managedObjectContext]; 
         
        NSPredicate *predicate = [NSPredicate predicateWithFormat:@"subjectID == %d", i]; 
        [fetchRequest setEntity:entity]; 
        [fetchRequest setPredicate:predicate]; 
         
        float subjectMax = [managedObjectContext countForFetchRequest:fetchRequest error:&error]; 
        NSLog(@"enrolled for Subject %d is %f", i, subjectMax); 
        [fetchRequest release]; 
         
        if (subjectMax > maxEnrolled)  
        { 
            maxEnrolled = subjectMax; 
        } 
    } 
     
    return maxEnrolled; 
}

'getTotalSubjects' simply gets a count of all the subjects in the core data store. 'getMaxEnrolled' loops through all the subjects and looks for the highest amount of students in each one and returns the highest value.

With these methods done, we can go back up to our 'viewDidLoad' method and add the following code below where we add the bar plot to our graph:

 
CPTXYPlotSpace *studentPlotSpace = (CPTXYPlotSpace *)[graph defaultPlotSpace]; 
[studentPlotSpace setXRange:[CPTPlotRange plotRangeWithLocation:CPTDecimalFromInt(0) length:CPTDecimalFromInt([self getTotalSubjects] + 1)]]; 
[studentPlotSpace setYRange:[CPTPlotRange plotRangeWithLocation:CPTDecimalFromInt(0) length:CPTDecimalFromInt([self getMaxEnrolled] + 1)]]; 
     
[[graph plotAreaFrame] setPaddingLeft:40.0f]; 
[[graph plotAreaFrame] setPaddingTop:10.0f]; 
[[graph plotAreaFrame] setPaddingBottom:120.0f]; 
[[graph plotAreaFrame] setPaddingRight:0.0f]; 
[[graph plotAreaFrame] setBorderLineStyle:nil]; 
     
CPTMutableTextStyle *textStyle = [CPTMutableTextStyle textStyle]; 
[textStyle setFontSize:12.0f]; 
[textStyle setColor:[CPTColor colorWithCGColor:[[UIColor grayColor] CGColor]]]; 
     
CPTXYAxisSet *axisSet = (CPTXYAxisSet *)[graph axisSet]; 
     
CPTXYAxis *xAxis = [axisSet xAxis]; 
[xAxis setMajorIntervalLength:CPTDecimalFromInt(1)]; 
[xAxis setMinorTickLineStyle:nil]; 
[xAxis setLabelingPolicy:CPTAxisLabelingPolicyFixedInterval]; 
[xAxis setLabelTextStyle:textStyle]; 
     
CPTXYAxis *yAxis = [axisSet yAxis]; 
[yAxis setMajorIntervalLength:CPTDecimalFromInt(1)]; 
[yAxis setMinorTickLineStyle:nil]; 
[yAxis setLabelingPolicy:CPTAxisLabelingPolicyFixedInterval];

Most of the above should be familiar from last time, however, instead of setting hard coded values for our x and y max plot range length, we are using our new methods to dynamically create the values. After that, we just set up some basic axis formatting and give the plot frame some padding so that the axis shows appropriately.

Now we need to provide the graph with data using the data source methods. Providing the number of records is simple:

 
#pragma mark - CPTBarPlotDataSourceMethods 
- (NSUInteger)numberOfRecordsForPlot:(CPTPlot *)plot 
{ 
    return [self getTotalSubjects]; 
}

Now to provide the x and y values for the records:

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

The above method is very similar to how we provide data to a scatter plot. We change the call to the data store so we get a count of all students enrolled in a subject. We also set the x value to +1. This is so the bar starts at '1' and provides a little bit of padding between the first bar and the y-axis line.

Save and run the project...you should see your bar graph!



Step 3: Finishing Touches

There is a bit more we can do to make this easier to look at. We want to give each bar a different color and provide the name of the subject as the x-axis label instead of a number. We can do the first with a CPTBarPlotDataSource method called 'barFillForBarPlot:recordIndex':

 
-(CPTFill *)barFillForBarPlot:(CPTBarPlot *)barPlot recordIndex:(NSUInteger)index 
{ 
 	CPTColor *areaColor = nil; 
     
    switch (index)  
    { 
        case 0: 
            areaColor = [CPTColor redColor]; 
            break; 
             
        case 1: 
            areaColor = [CPTColor blueColor]; 
            break; 
         
        case 2: 
            areaColor = [CPTColor orangeColor]; 
            break; 
             
        case 3: 
            areaColor = [CPTColor greenColor]; 
            break; 
             
        default: 
            areaColor = [CPTColor purpleColor]; 
            break; 
    } 
     
	CPTFill *barFill = [CPTFill fillWithColor:areaColor]; 
     
    return barFill; 
}

This will return a different color for each bar in our graph. If you wanted to make it even more stylish, you could actually make it a gradient. It's also a part of our graph that isn't fully dynamic, because if someone adds a new subject it will use the default color. Perhaps a way to get around this in a real life application would be to have a color when creating a new subject that's stored in the data store


Finally, let's add some custom x-axis titles. To do this, we need to work with the x-axis. Locate where we set up the x-axis and modify the code to do the following:

 
CPTXYAxis *xAxis = [axisSet xAxis]; 
[xAxis setMajorIntervalLength:CPTDecimalFromInt(1)]; 
[xAxis setMinorTickLineStyle:nil]; 
[xAxis setLabelingPolicy:CPTAxisLabelingPolicyNone]; 
[xAxis setLabelTextStyle:textStyle]; 
[xAxis setLabelRotation:M_PI/4]; 
     
NSArray *subjectsArray = [self getSubjectTitlesAsArray]; 
     
[xAxis setAxisLabels:[NSSet setWithArray:subjectsArray]];

There are a few changes in the above code. First, we are changing the labeling policy to none. This will ensure that CorePlot doesn't print out the label itself and will instead use what we give it. Next, we set the label rotation so that it lines up with the graph better. Finally, we set the 'Axis labels' property which takes an NSSet of NSString values. We create the NSSet using an NSArray created by the method 'getSubjectTitlesAsArray'. That method doesn't exist yet, so let's create it. Add the declaration to the top of the .m file and then write the following implementation:

 
- (NSArray *)getSubjectTitlesAsArray 
{ 
    NSError *error = nil; 
     
    NSFetchRequest *request = [[NSFetchRequest alloc] init]; 
    NSSortDescriptor *sortDescriptor = [NSSortDescriptor sortDescriptorWithKey:@"subjectID" ascending:YES]; 
    NSEntityDescription *entity = [NSEntityDescription entityForName:@"STSubject" inManagedObjectContext:managedObjectContext];     
    [request setEntity:entity]; 
    [request setSortDescriptors:[NSArray arrayWithObject:sortDescriptor]]; 
    [request setResultType:NSDictionaryResultType]; 
    [request setReturnsDistinctResults:NO]; 
    [request setPropertiesToFetch :[NSArray arrayWithObject:@"subjectName"]]; 
     
    NSArray *titleStrings = [managedObjectContext executeFetchRequest:request error:&error]; 
    NSMutableArray *labelArray = [NSMutableArray array]; 
     
    CPTMutableTextStyle *textStyle = [CPTMutableTextStyle textStyle]; 
    [textStyle setFontSize:10]; 
     
    for (int i = 0; i < [titleStrings count]; i++) 
    { 
        NSDictionary *dict = [titleStrings objectAtIndex:i]; 
         
        CPTAxisLabel *axisLabel = [[CPTAxisLabel alloc] initWithText:[dict objectForKey:@"subjectName"] textStyle:textStyle]; 
        [axisLabel setTickLocation:CPTDecimalFromInt(i + 1)]; 
        [axisLabel setRotation:M_PI/4]; 
        [axisLabel setOffset:0.1]; 
        [labelArray addObject:axisLabel]; 
        [axisLabel release]; 
    } 
     
    return [NSArray arrayWithArray:labelArray]; 
}

There's a lot going on in the above code. In order to give a graph custom labels, we need to pass an NSSet containing objects of type 'CPTAxisLabel'. First, we get an array of all the subject names ordered by subjectID so it will be in the same order as the graph. Then, for the amount of names we get back, we loop through and create a CPTAxisLabel with the subject name string and a predefined text style. The 'tick location' is which tick it will appear for. We need to add 1 to our value because we are starting our first bar at 1 instead of 0. We then set a rotation and offset and add it to an array. Finally, we return the array of axisLabels.

If we save and run the project, we have a completed, colorful bar graph with custom labels!



Next Time

We've learned a fair amount about CorePlot this session. We've learned how to create a bar graph, modify the bar's colors, and even add custom labels to the axis.

Next time, we'll cover how to make an awesome pie chart that shows the same data as the bar graph. Catch you next time!

Advertisement
Related Posts
  • Code
    iOS SDK
    Securing and Encrypting Data on iOSPs8e2e preview image@2x
    Whether you're creating a mobile application or a web service, keeping sensitive data secure is important and security has become an essential aspect of every software product. In this tutorial, I will show you how to safely store user credentials using the application's keychain and we'll take a look at encrypting and decrypting user data using a third party library.Read More…
  • Code
    iOS SDK
    Working with NSURLSession: Part 4E548b preview image@2x
    In the previous tutorial, we started creating a simple podcast client to put what we've learned about NSURLSession into practice. So far, our podcast client can query the iTunes Search API, download a podcast feed, and display a list of episodes. In this tutorial, we zoom in on another interesting aspect of NSURLSession, out-of-process downloads. Let me show you how this works.Read More…
  • Code
    iOS SDK
    Working with NSURLSession: Part 3E548b preview image@2x
    In the previous tutorials, we explored the fundamentals of the NSURLSession API. There is one other feature of the NSURLSession API that we haven't look into yet, that is, out-of-process uploads and downloads. In the next two tutorials, I will show you how to create a very simple podcast client that enables background downloads.Read More…
  • Code
    iOS SDK
    Objective-C Succinctly: Data Types0e5ds8 preview image@2x
    Objective-C has two categories of data types. First, remember that Objective-C is a superset of C, so you have access to all of the native C data types like char, int, float, etc. Objective-C also defines a few of its own low-level types, including a Boolean type. Let's call all of these "primitive data types."Read More…
  • Code
    iOS SDK
    Working with CorePlot: Creating a Pie ChartCode
    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! Read More…
  • Code
    iOS SDK
    Working with CorePlot: Plot FundamentalsCode
    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!Read More…