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.

没有评论:

发表评论