显示标签为“Graph”的博文。显示所有博文
显示标签为“Graph”的博文。显示所有博文

2011年11月3日星期四

Creating a Graph With Quartz 2D: Part 3

Creating a Graph With Quartz 2D: Part 3:
To create a line graph, I am going to reuse the same project that we used for drawing bars in the previous part of the series. We won’t need the logic for drawing bars anymore, so comment out a dozen of lines of code in the end of the drawRect method, those that deal with bars.
I’ve also changed the value of kOffsetX to 0, so that the line graph starts from the left edge of the view. We’ll need to add a couple of values to the dataset, and you can choose any values between 0 and 1. Here is what I’ve chosen:
float data[] = {0.7, 0.4, 0.9, 1.0, 0.2, 0.85, 0.11, 0.75, 0.53, 0.44, 0.88, 0.77, 0.99, 0.55};
Create a new empty method, making sure that it goes before the drawRect:
- (void)drawLineGraphWithContext:(CGContextRef)ctx
{
}
We’ll be gradually filling this method with code. The first step is to set up the environment, in this case to define the width and the color of the line we are going to use. The following two lines of code shouldn’t surprise you:
CGContextSetLineWidth(ctx, 2.0);
CGContextSetStrokeColorWithColor(ctx, [[UIColor colorWithRed:1.0 green:0.5 blue:0 alpha:1.0] CGColor]);
You are already familiar with drawing paths – we’ve drawn bars as paths – so the following lines of code, although slightly different, should be fully comprehensible:
int maxGraphHeight = kGraphHeight - kOffsetY;
CGContextBeginPath(ctx);
CGContextMoveToPoint(ctx, kOffsetX, kGraphHeight - maxGraphHeight * data[0]);
for (int i = 1; i < sizeof(data); i++)
{
CGContextAddLineToPoint(ctx, kOffsetX + i * kStepX, kGraphHeight - maxGraphHeight * data[i]);
}
CGContextDrawPath(ctx, kCGPathStroke);
This is all we need to draw a simple line graph. Run the app, and you should see the following:
Quartz 2D Part III Figure 1
Figure 1
For some simple cases this graph might be acceptable, but there are a number of ways in which we can enhance it. One of them is to make the data points more noticeable.

Emphasising Data Points

One popular solution is to draw a little circle on top of each datapoint, so let’s do that. First of all, we’ll want the circles to be filled with color, so add the following line of code to the very end of the drawLineGraphWithContext method:
CGContextSetFillColorWithColor(ctx, [[UIColor colorWithRed:1.0 green:0.5 blue:0 alpha:1.0] CGColor]);
Next, let’s define the circle radius in the header file:
#define kCircleRadius 3
In Quartz 2D, to draw a circle or ellipse, we first need to create a rectangle (CGRect) that will define that circle’s dimensions. The simplest way to create such a rectangle is to use the CGRectMake function that takes four parameters: the x and y coordinates of the upper left corner of the rectangle, then its width and height.
We’ll be creating the rectangles in such a way that their centers coincide with the points through which we’ve just drawn the line graph. Then we’ll inscribe a circle into each of the rectangles, and finally, we’ll stroke and fill the circles. All in all, the code looks like this:
for (int i = 1; i < sizeof(data) - 1; i++)
{
float x = kOffsetX + i * kStepX;
float y = kGraphHeight - maxGraphHeight * data[i];
CGRect rect = CGRectMake(x - kCircleRadius, y - kCircleRadius, 2 * kCircleRadius, 2 * kCircleRadius);
CGContextAddEllipseInRect(ctx, rect);
}
CGContextDrawPath(ctx, kCGPathFillStroke);
Add it to the end of the drawLineGraphWithContext method, and this is the result that you should see:
Quartz 2D Part III Figure 2
Figure 2
The next enhancement we might want to make is filling the space underneath the graph with a color.

Filling the Graph

The steps needed to create a graph filled with color are very similar to those for creating the graph line itself, the only difference is that we need to create a closed path. Let’s see how to do that.
At the beginning of drawLineGraphWithContext method, right after defining the maxGraphHeight, let’s specify a fill color – the same color we are using for drawing the graph itself, but semi-transparent:
CGContextSetFillColorWithColor(ctx, [[UIColor colorWithRed:1.0 green:0.5 blue:0 alpha:0.5] CGColor]);
Then we basically draw the graph, but add a few lines to surround a closed space. Finally, we fill the resulting path but we don’t stroke it. Follow the code and you’ll see what’s going on:
CGContextBeginPath(ctx);
CGContextMoveToPoint(ctx, kOffsetX, kGraphHeight);
CGContextAddLineToPoint(ctx, kOffsetX, kGraphHeight - maxGraphHeight * data[0]);
for (int i = 1; i < sizeof(data); i++)
{
CGContextAddLineToPoint(ctx, kOffsetX + i * kStepX, kGraphHeight - maxGraphHeight * data[i]);
}
CGContextAddLineToPoint(ctx, kOffsetX + (sizeof(data) - 1) * kStepX, kGraphHeight);
CGContextClosePath(ctx);
CGContextDrawPath(ctx, kCGPathFill);
After the fill is complete, we draw the graph line itself and the circles, as before, on top of it. If you run the app now, you’ll see that the graph became a bit nicer:
Quartz 2D Part III Figure 3
Figure 3
To make it nicer still, we might want to fill the graph with a gradient. You already know how to create a gradient, so it should be easier this second time around.

Filling the Graph with a Gradient

Here is the code that prepares the gradient, with no comments this time. Insert it right before the code that we’ve written in the previous section for creating a solid fill:
CGGradientRef gradient;
CGColorSpaceRef colorspace;
size_t num_locations = 2;
CGFloat locations[2] = {0.0, 1.0};
CGFloat components[8] = {1.0, 0.5, 0.0, 0.2,  // Start color
1.0, 0.5, 0.0, 1.0}; // End color
colorspace = CGColorSpaceCreateDeviceRGB();
gradient = CGGradientCreateWithColorComponents(colorspace, components, locations, num_locations);
CGPoint startPoint, endPoint;
startPoint.x = kOffsetX;
startPoint.y = kGraphHeight;
endPoint.x = kOffsetX;
endPoint.y = kOffsetY;
Now, we are going to reuse the closed path we’ve created in the previous section. This time it will work as a clipping path. Comment out this line:
CGContextDrawPath(ctx, kCGPathFill);
and replace it with the other two:
CGContextSaveGState(ctx);
CGContextClip(ctx);
We are ready to pour the gradient into the graph now. Go ahead:
CGContextDrawLinearGradient(ctx, gradient, startPoint, endPoint, 0);
Finally, do the cleanup:
CGContextRestoreGState(ctx);
CGColorSpaceRelease(colorspace);
CGGradientRelease(gradient);
Here is the result that you should see:
Quartz 2D Part III Figure 4
Figure 4
Not bad, is it? And of course, you can tweak the gradient if you wish.
Now we know how to draw both a bar graph and a line graph. They look good but they are not interactive yet. It would be nice if, say, when the user taps a bar, the graph would display a message appropriate for that bar. This is exactly what we are going to deal with in the next part of the series.

Quartz 2D Index

Alexander Kolesnikov’s series on Creating a Graph using Quartz 2D was split into 5 parts. You can refer to the series using the Quartz 2D Tag and access the individual articles using the links below.

Creating a Graph With Quartz 2D: Part 2

Creating a Graph With Quartz 2D: Part 2:
In the first part of my series Creating a Graph with Quartz 2D I explained the background work. Bar graphs are a popular kind of graph, so let’s learn how to draw them.
First of all, I suggest commenting out the lines of code that draw the background image. We know how to do it if needed but let’s keep things as simple as possible here.
Second, it might be a good idea to leave some space between our bars, so let’s increase the horizontal step. In GraphView.h, modify the kStepX definition:
#define kStepX 70

Drawing Bars

Let’s add to GraphView.m a method that will draw one bar at a time. Make sure this method is defined before drawRect:
- (void)drawBar:(CGRect)rect context:(CGContextRef)ctx
{
}
We are passing a rectangle into the method, to fill it with the bar, and a graphics context to draw in. I find that a simple rectangle filled with a nice gradient works best for bars, so let’s learn how to draw one. If you prefer, you can modify the code that follows and draw, say, a rectangle with rounded corners but, once again, I prefer to keep things simple.
We are going to draw the rectangle as a path, therefore all the drawing code will be surrounded by the following two lines:
CGContextBeginPath(ctx);
...
CGContextClosePath(ctx);
Code for defining a gradient can be somewhat verbose, so to begin with, let’s fill our rectangles with a solid color. Here is the single line of code that will prepare the environment for drawing:
CGContextSetGrayFillColor(ctx, 0.2, 0.7);
The second parameter specifies how dark we want the fill to be, with 0 meaning black and 1 meaning white. In our case, it’s dark gray. The last parameter defines the transparency of the fill, with 0 being completely transparent and 1 being completely opaque. In our case, it’s 70% opaque.
The actual drawing takes four lines of code, and I believe you can easily guess what’s going on here from the names of the functions:
CGContextMoveToPoint(ctx, CGRectGetMinX(rect), CGRectGetMinY(rect));
CGContextAddLineToPoint(ctx, CGRectGetMaxX(rect), CGRectGetMinY(rect));
CGContextAddLineToPoint(ctx, CGRectGetMaxX(rect), CGRectGetMaxY(rect));
CGContextAddLineToPoint(ctx, CGRectGetMinX(rect), CGRectGetMaxY(rect));
Finally, in step 3, we need to commit what was drawn:
CGContextFillPath(ctx);
Here is the completed method for drawing a solid bar:
- (void)drawBar:(CGRect)rect context:(CGContextRef)ctx
{
CGContextBeginPath(ctx);
CGContextSetGrayFillColor(ctx, 0.2, 0.7);
CGContextMoveToPoint(ctx, CGRectGetMinX(rect), CGRectGetMinY(rect));
CGContextAddLineToPoint(ctx, CGRectGetMaxX(rect), CGRectGetMinY(rect));
CGContextAddLineToPoint(ctx, CGRectGetMaxX(rect), CGRectGetMaxY(rect));
CGContextAddLineToPoint(ctx, CGRectGetMinX(rect), CGRectGetMaxY(rect));
CGContextClosePath(ctx);
CGContextFillPath(ctx);
}

Graph Data

Next, we need to take care of the data displayed by the graph. Typically, the data could be delivered by some kind of web service. This could be, for example, the number of visitors of your website per month. However, for simplicity’s sake, we are going to hard-code the data, with the values between 0 and 1 where 1 will mean a bar taking the whole height of the graph and 0 meaning no bar at all. Place this line of code somewhere outside of any method in GraphView.m (the values are arbitrary, you can use any other):
float data[] = {0.7, 0.4, 0.9, 1.0, 0.2, 0.85, 0.11, 0.75, 0.53, 0.44, 0.88, 0.77};
Let’s add a couple of constants that will help us to position and size the bars:
#define kBarTop 10
#define kBarWidth 40
Finally, we need to draw the bars corresponding to the test values. In the very end of drawRect, place the following code:
// Draw the bars
float maxBarHeight = kGraphHeight - kBarTop - kOffsetY;
for (int i = 0; i < sizeof(data); i++)
{
float barX = kOffsetX + kStepX + i * kStepX - kBarWidth / 2;
float barY = kBarTop + maxBarHeight - maxBarHeight * data[i];
float barHeight = maxBarHeight * data[i];
CGRect barRect = CGRectMake(barX, barY, kBarWidth, barHeight);
[self drawBar:barRect context:context];
}
You should be able to understand what’s going on here without additional explanations, just bear in mind that Y coordinate increases from top to bottom. And here is the result that we’ve achieved so far:
Quartz 2D Part II Figure 1
Figure 1
The graph already looks quite good and can be useful for some applications as it is. You might want to use some other color instead of gray, but that’s easy to do. Here is a link to the CGContext Reference, where you will find all of the methods that you might need.
However, the graph will look dramatically better if we fill the bars with a gradient. Let’s see how this can be done.

Gradient Fills

The way that gradients are defined and used in Quartz is somewhat verbose, but it gives us a lot of power. Here is all we need to know to fill our bars with gradients.
First, we need to decide how many colors we are going to use for the gradient. We can use any number, but three colors should be sufficient for our purposes. Let’s define them by listing their red, green, blue and alpha components:
CGFloat components[12] = {0.2314, 0.5686, 0.4, 1.0,  // Start color
0.4727, 1.0, 0.8157, 1.0, // Second color
0.2392, 0.5686, 0.4118, 1.0}; // End color
Next, we need to decide where to position these colors in the gradient, with 0 meaning the beginning of the pattern, and 1 meaning the end of the pattern. Here is one possible distribution for our three colors:
CGFloat locations[3] = {0.0, 0.33, 1.0};
We’ll also need to explicitly define the number of locations:
size_t num_locations = 3;
Finally, we need to create a colorspace, and then, using all the prepared information, we can construct the gradient:
CGColorSpaceRef colorspace = CGColorSpaceCreateDeviceRGB();
CGGradientRef gradient = CGGradientCreateWithColorComponents(colorspace, components, locations, num_locations);
When you don’t need the gradient anymore, you should release both the gradient and the colorspace:
CGColorSpaceRelease(colorspace);
CGGradientRelease(gradient);
Just before using the gradient, we need to specify where the pattern will start and end, in terms of the graph space. We use the CGRect that was passed to the method to figure out these two points:
CGPoint startPoint = rect.origin;
CGPoint endPoint = CGPointMake(rect.origin.x + rect.size.width, rect.origin.y);
And, finally, here is the line that does the actual drawing:
// Draw the gradient
CGContextDrawLinearGradient(ctx, gradient, startPoint, endPoint, 0);
Here is all the code that prepares, draws and releases the gradient:
// Prepare the resources
CGFloat components[12] = {0.2314, 0.5686, 0.4, 1.0,  // Start color
0.4727, 1.0, 0.8157, 1.0, // Second color
0.2392, 0.5686, 0.4118, 1.0}; // End color
CGFloat locations[3] = {0.0, 0.33, 1.0};
size_t num_locations = 3;
CGColorSpaceRef colorspace = CGColorSpaceCreateDeviceRGB();
CGGradientRef gradient = CGGradientCreateWithColorComponents(colorspace, components, locations, num_locations);
CGPoint startPoint = rect.origin;
CGPoint endPoint = CGPointMake(rect.origin.x + rect.size.width, rect.origin.y);
// Draw the gradient
CGContextDrawLinearGradient(ctx, gradient, startPoint, endPoint, 0);
// Release the resources
CGColorSpaceRelease(colorspace);
CGGradientRelease(gradient);
At this point, we might be tempted to discard the code that we used before for drawing and filling the bar, and simply draw the gradient. If we do that, however, the result will be different to what we expected:
Quartz 2D Part II Figure 2
Figure 2
Looks like the gradient doesn’t really understand how much space it is supposed to take up. We need to somehow limit the drawing area to the dimensions of the bar. This is where clipping path becomes useful.

Clipping Paths

Here is how we are going to do it. First, we’ll draw the bar as a filled rectangle, like we did before, but instead of committing the drawing and making it visible, we’ll tell the graphics context: the bar we’ve just drawn defines the only space where you are allowed to draw from now on. This long phrase can be translated into a rather short line of code:
CGContextClip(ctx);
We should be able to lift the limitation immediately after the bar drawing is done. For this, we are going to tell the context to remember its state of unlimited freedom, right before applying the clipping path:
CGContextSaveGState(ctx);
And right after the gradient was drawn with the use of the clipping path, we are going to restore the initial state of the context:
CGContextRestoreGState(ctx);
Here is the complete solution for drawing a bar with a gradient fill, with all the steps that we mentioned above, in the correct order:
- (void)drawBar:(CGRect)rect context:(CGContextRef)ctx
{
// Prepare the resources
CGFloat components[12] = {0.2314, 0.5686, 0.4, 1.0,  // Start color
0.4727, 1.0, 0.8157, 1.0, // Second color
0.2392, 0.5686, 0.4118, 1.0}; // End color
CGFloat locations[3] = {0.0, 0.33, 1.0};
size_t num_locations = 3;
CGColorSpaceRef colorspace = CGColorSpaceCreateDeviceRGB();
CGGradientRef gradient = CGGradientCreateWithColorComponents(colorspace, components, locations, num_locations);
CGPoint startPoint = rect.origin;
CGPoint endPoint = CGPointMake(rect.origin.x + rect.size.width, rect.origin.y);
// Create and apply the clipping path
CGContextBeginPath(ctx);
CGContextSetGrayFillColor(ctx, 0.2, 0.7);
CGContextMoveToPoint(ctx, CGRectGetMinX(rect), CGRectGetMinY(rect));
CGContextAddLineToPoint(ctx, CGRectGetMaxX(rect), CGRectGetMinY(rect));
CGContextAddLineToPoint(ctx, CGRectGetMaxX(rect), CGRectGetMaxY(rect));
CGContextAddLineToPoint(ctx, CGRectGetMinX(rect), CGRectGetMaxY(rect));
CGContextClosePath(ctx);
CGContextSaveGState(ctx);
CGContextClip(ctx);
// Draw the gradient
CGContextDrawLinearGradient(ctx, gradient, startPoint, endPoint, 0);
CGContextRestoreGState(ctx);
// Release the resources
CGColorSpaceRelease(colorspace);
CGGradientRelease(gradient);
}

On Your Own

There is space for enhancement, of course. As we are using the same gradient again and again, it would be more efficient to create it just once and then reuse it for drawing as many bars as needed, rather than recreate the gradient for each bar. However, let me leave this refactoring to you. Here is what we should see when running this code:
Quartz 2D Part II Figure 3
Figure 3
We now have a bar graph that is close to completion. We’ll need some labels, and we’ll need to respond to touches but these topics will be covered in a later part of the series. Another popular kind of graph is a line graph, and in the next part of the series, we’ll learn how to draw those, including gradients, plus a few other nice tweaks.

Quartz 2D Index

Alexander Kolesnikov’s series on Creating a Graph using Quartz 2D was split into 5 parts. You can refer to the series using the Quartz 2D Tag and access the individual articles using the links below.

Creating a Graph With Quartz 2D Part 1

Creating a Graph With Quartz 2D:
When I joined the team I have been working with recently, they were trying to create a graph using Core Plot, a popular third party library. It didn’t go well though, there were two big problems. First, they couldn’t use a custom image for the graph’s background, as was required by the designer. Second, the quality of scrolling was unacceptable.
By that time, I already had a couple of graphs under my belt, so I suggested an alternative approach: to draw the graph from scratch using nothing else but Quartz 2D – a graphics rendering API created by Apple, a part of the Core Graphics.
At first, this solution didn’t seem optimal. After all, many third party libraries were created to shield developers from using low level APIs, to save our time and effort. However, working with Quartz 2D isn’t that hard. You just need to know how to do certain things, and once you’ve learnt them, you can move forward quite quickly. I spent just a couple of weeks creating a sophisticated solution that implemented every little wish of the designer.
In this series of articles, I am going to share with you the approach I am using for drawing graphs. Let’s put together some requirements for our future solution.
  • It should scroll smoothly, and easily resize for different datasets.
  • It should respond to touch by displaying an appropriate information.
  • It should look well and be flexible enough to make our designers happy.

Creating the Project

We need to create an Xcode project that will host our gradually emerging graph. The View-based Application template will work just fine, and you can give the project any name you find appropriate. I named it MyGraph.
Quartz 2D Figure 2
Create the Project
After the template project is generated and saved to a location of your choice, our first task is to create a simple user interface. Let’s create a graph that occupies the whole view in landscape orientation, but only the upper part of the view in portrait orientation.
We can decide later to use the lower part of the portrait view for some controls, or for displaying our data as a table. The important requirement is that the graph should scroll in whatever the amount of space is given to it.
In Xcode, select the MyGraphViewController.xib file. In the Library, find the Scroll View and drag and drop an instance of it to the main view. With any luck it should snap into place, see the below image to check your progress.
Quartz 2D Figure 3
Add Scroll View
Resize the UIScrollView so that it occupied the top part of the portrait view and make its height 300 pixels. That’s the height of the landscape view (320 pixels) minus the status bar. Also disable a couple of autosizing handles so that the scroll view resized properly with the change of orientation:
Quartz 2D Figure 4
Resize the Scroll View
For drawing, we are going to use a simple view or, to be precise, a subclass of UIView class. In the Library, find View, then drag and drop one right on top of the UIScrollView. Expand the hierarchy of the objects in the MyGraphViewController.xib file and make sure that the newly added view became a child of the Scroll View.
Also notice that I’ve labeled the two views Main View and Graph View appropriately. This is very easy to do – just press Enter with the view is selected in the hierarchy and give it a new name – but can become incredibly convenient as the number of views in the graph grows.
Quartz 2D Figure 5
View Hierarchy
We’ll want our Graph View to be wide enough, so that it could scroll in the Scroll View, so let’s give it an initial width of 900 pixels, as seen in the image below. This brings the initial setup to completion, and we can start drawing.
Quartz 2D Figure 6
Set Initial Size

Drawing the Grid Lines

We’ll want to start from something simple, something that will just confirm to us that everything is right. A straight line is about the simplest thing one can draw, and most graphs have some sort of grid lines as a visual aid. It would be reasonable then, to start from drawing a bunch of grid lines.
But where exactly we are going to draw? So far, our Graph View is just a stock UIView that draws nothing. What we need is to extend the UIView, and in the new class override the drawRect method: that’s where our drawing code will go.
Add to the project a new Objective-C class, make it a subclass of UIView and give it a descriptive name, say GraphView. Next, select the view that we’ve labeled Graph View in the hierarchy of the MyGraphViewController.xib file and change its class to the new one, GraphView:
Quartz 2D Figure 7
Change Graph View Class
In GraphView.m, you will see that the - (void)drawRect:(CGRect)rect method is commented out. Remove the comments and leave the body of the method empty. Before we’ll be able to draw anything, we’ll need to obtain a reference to the graphics context, so let’s add the very first line of code and the method will look like this:
- (void)drawRect:(CGRect)rect
{
CGContextRef context = UIGraphicsGetCurrentContext();
}
Our drawing code is going to use a lot of numbers – for example, it will need to know the width and the height of the graph. It is a good idea to define all those numbers as constants in the header file GraphView.h. This way, whenever you want to change a value, you’ll be able to find it easily. Switch to GraphView.h and add the following definitions right after the import statement:
#define kGraphHeight 300
#define kDefaultGraphWidth 900
#define kOffsetX 10
#define kStepX 50
#define kGraphBottom 300
#define kGraphTop 0
First, we define two constants for the width and the height of the graph, they correspond to the dimensions of the Graph View in the XIB file. We’ll be changing the width of the graph dynamically, but it’s good to have a default value.
As for kStepX and kOffsetX, they define the horizontal distance between the vertical grid lines and the offset for the first line respectively.
Finally, we define the coordinates for the top and the bottom of the graph. Currently, they are the same as the top and the bottom of the view but we might want to change that with time. (Note that the vertical coordinate starts at the top of the view and increases as we go down.)
We can start drawing now. In Quartz 2D, drawing is done in three steps:
  1. Preparation. This is where we define which resources and dimensions we are going to use: colors, fonts, dimensions and so on.
  2. Actual drawing: our code draws lines, curves etc.
  3. Commit. We need to tell Quartz 2D that we’ve done with this step of drawing and it can make our art visible. If we omit this step, nothing will appear in the view.
Usually, to complete the drawing we need to repeat these three steps multiple times. For the first step, let’s define the thickness and the color of the grid lines. Add the following two lines to the drawRect method:
CGContextSetLineWidth(context, 0.6);
CGContextSetStrokeColorWithColor(context, [[UIColor lightGrayColor] CGColor]);
Next, we’ll do the actual drawing of as many vertical lines as can fit in our view:
// How many lines?
int howMany = (kDefaultGraphWidth - kOffsetX) / kStepX;
// Here the lines go
for (int i = 0; i < howMany; i++)
{
CGContextMoveToPoint(context, kOffsetX + i * kStepX, kGraphTop);
CGContextAddLineToPoint(context, kOffsetX + i * kStepX, kGraphBottom);
}
Thanks to the descriptive names of Core Graphics functions and our constants, I don’t think there is a need to explain what this code does. Finally, we commit our drawing, and this is the required third step:
CGContextStrokePath(context);
Here is the complete code of the drawRect method at this point:
- (void)drawRect:(CGRect)rect
{
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetLineWidth(context, 0.6);
CGContextSetStrokeColorWithColor(context, [[UIColor lightGrayColor] CGColor]);
// How many lines?
int howMany = (kDefaultGraphWidth - kOffsetX) / kStepX;
// Here the lines go
for (int i = 0; i < howMany; i++)
{
CGContextMoveToPoint(context, kOffsetX + i * kStepX, kGraphTop);
CGContextAddLineToPoint(context, kOffsetX + i * kStepX, kGraphBottom);
}
CGContextStrokePath(context);
}
Further on, I won’t show the complete code anymore as it will be getting longer and longer. Basically, whatever we’ll be adding after this, will always go to the bottom of the method, unless specified otherwise. If you want to see the completed version, you are welcome to checkout the Github BuildMobile Quartz 2D Graph repository.
Run the application, and you should see the vertical gray lines in the graph view.
Quartz 2D Figure 8
Grid Lines Portrait View
However, when we turn the device to the landscape orientation, the graph doesn’t follow the rotation. We should explicitly declare that we do support change of orientation. In MyGraphViewController.m, change the shouldAutorotateToInterfaceOrientation method so that it looked like this.
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
return YES;
}
Try to run the app again, and this time the future graph will follow the rotation of the device.
Quartz 2D Figure 9
Grid Lines Landscape View
Unfortunately, the graph doesn’t scroll yet. That’s because we need to explicitly tell the UIScrollView what’s the size of its content. And to be able to do that, we need to have a pointer to the UIScrollView.

Enabling Scrolling

In Xcode, select the MyGraphViewController.xib file, make sure that the view hierarchy is visible and press the Show the Assistant editor button in the Editor section of the toolbar. The contents of the MyGraphViewController.h header file should appear in the right half of the split view. Ctrl-drag from the Scroll View in the hierarchy to the source code on the right hand side so that the insertion point was inside the class declaration, as the following screenshot demonstrates:
Quartz 2D Figure 10
Outlet for Scroll View
Release the mouse button, give the new outlet a name, say, scroller, and Xcode will automatically write all the code required to properly obtain a pointer to the Scroll View and maintain it. Here is what the resulting code should look like in MyGraphViewController.h. A few lines of code will be added to MyGraphViewController.m as well.
#import <UIKit/UIKit.h>
@interface MyGraphViewController : UIViewController {
UIScrollView *scroller;
}
@property (nonatomic, retain) IBOutlet UIScrollView *scroller;
@end
Now, as soon as the app is ready to be displayed, we need to tell the Scroll View what’s the size of its child Graph View. The best place to do that is in the viewDidLoad method of the MyGraphViewController class. This method is probably already present in MyGraphViewController.m but it might be commented out. Remove the comments and add the following line of code:
- (void)viewDidLoad
{
[super viewDidLoad];
scroller.contentSize = CGSizeMake(kDefaultGraphWidth, kGraphHeight);
}
Now you can run the app, and the Graph View will scroll nicely, in both portrait and landscape orientation.

Tweaking the Grid Lines

To be honest with you, I decided to draw the grid lines before enabling scrolling so that you could easily notice whether or not the graph needed to be scrolled. Now that the grid lines are there, we might want to tweak them a little bit.
One obvious addition is the horizontal grid lines. That’s easy. First add a couple of new constant definitions to GraphView.h
#define kStepY 50
#define kOffsetY 10
Then add the following lines of code right after drawing the vertical grid lines, before the line that commits the drawing.
int howManyHorizontal = (kGraphBottom - kGraphTop - kOffsetY) / kStepY;
for (int i = 0; i <= howManyHorizontal; i++)
{
CGContextMoveToPoint(context, kOffsetX, kGraphBottom - kOffsetY - i * kStepY);
CGContextAddLineToPoint(context, kDefaultGraphWidth, kGraphBottom - kOffsetY - i * kStepY);
}
If you run the application, you will see both the vertical and the horizontal grid lines.
Quartz 2D Figure 11
Vertical and Horizontal Grid Lines
In many cases, this kind of grid line will be good enough, but your designers might not be happy until you make the lines dashed. Thankfully, we can do that easily by simply adding a couple of lines of code to the preparation step. Right underneath the line that sets the stroke color, add the following code.
CGFloat dash[] = {2.0, 2.0};
CGContextSetLineDash(context, 0.0, dash, 2);
The dash array specifies that there are two elements in the pattern: a dash and an empty space after it. The last parameter of the CGContextSetLineDash function, 2, is the number of elements in the dash array. Knowing this, you can experiment with different types of dash patterns to create something that is appropriate for your purposes. If you run this code as it is, you should see the grid lines in the following screenshot.
Quartz 2D Figure 12
Dotted Grid Lines
Any other lines that we are going to draw later won’t be dashed, so we need to disable the dash that we’ve set up before. To do that, insert the following line of code right after the line that commits the drawing.
CGContextSetLineDash(context, 0, NULL, 0); // Remove the dash
Now we’ve prepared everything we might need before actually drawing the graph. The only other enhancement we might want to add is a graphical background for the graph, possibly crafted by our designers.

Adding a Graphical Background

I am not a designer, so the background I’ve created is very simple. The file is named background.png, and you will find it in the code on GitHub. Alternatively, you can create a graphic of your own, it should be 300 pixels tall and 900 pixels wide.
Add the background file to the project. I have created a new group named Artwork and placed the file into this group. Next, we need to insert three lines of code before and grid line drawing was done. Put them after the first line of this method, the one where we obtain a reference to the graphics context:
CGContextRef context = UIGraphicsGetCurrentContext();
// Draw the background image
UIImage *image = [UIImage imageNamed:@"background.png"];
CGRect imageRect = CGRectMake(0, 0, image.size.width, image.size.height);
CGContextDrawImage(context, imageRect, image.CGImage);
...
Run the project, and you will see both the background and the grid lines:
Quartz 2D Figure 13
Background Complete
Admittedly, using both a graphical background and grid lines in this particular case looks like an overkill. You might decide to leave either the background or the grid lines, but at least you know how to draw both.
Note: In this particular case, the background is very simple. If it was a more complex drawing, you would notice that it’s being drawn upside down. We’ll learn how to deal with this later in the article, for now let’s just leave it as it is.
Now that all the background work is completed, we can start drawing the actual graph, and this is exactly what we shall do in the next part of the article. Grab the Quartz 2D Graph code from the GitHub Repo and leave us your thoughts and questions in the comments. See you soon.