2011年11月3日星期四

Creating a Graph With Quartz 2D: Part 5

Creating a Graph With Quartz 2D: Part 5:
In this series of articles I am discussing the creation of charts and graphs using nothing more than Quartz 2D, a graphics rendering API created by Apple, which is a part of Core Graphics. You might wish to get up to speed with Part 1, Part 2, Part 3 and Part 4.

Drawing Text

Finally, we are going to write some text on the graph. You might be wondering why I delayed such a simple task for so long. Indeed, it doesn’t take a lot of effort to draw a string in Quartz 2D. Let’s try it: in GraphView, in the end of drawRect add the following snippet (it doesn’t matter which kind of graph you’ll be displaying at this stage):
// Drawing text
CGContextSelectFont(context, "Helvetica", 44, kCGEncodingMacRoman);
CGContextSetTextDrawingMode(context, kCGTextFill);
CGContextSetFillColorWithColor(context, [[UIColor colorWithRed:0 green:0 blue:0 alpha:1.0] CGColor]);
NSString *theText = @"Hi there!";
CGContextShowTextAtPoint(context, 100, 100, [theText cStringUsingEncoding:NSUTF8StringEncoding], [theText length]);
Nothing complex here: we are selecting a font, a color, a drawing mode and then drawing a random string somewhere inside the graph. How do you think the result will look? Let’s see:
Quartz 2D Part V Figure 1
Figure 1
Oops! Not exactly what we wanted: the text is drawn upside down. That’s because in the system of coordinates used in iOS the vertical coordinate goes from top to bottom. Basically, everything is drawn upside down. You might have noticed this already if you were trying to use a more or less complex graphical background.
We can keep this peculiarity in mind when drawing the graph, and adjust the logic accordingly. We could use a different background after all, but when it comes to drawing text, we have to face the problem.
Fortunately, the solution isn’t difficult. All we need to do is transform the system of coordinates in such a way that the vertical axis starts at the bottom of the graph and goes upwards – and there are a few functions that allow us to do exactly that. In fact, we can resolve our problem with a single line of code. Insert it before any text drawing is done:
CGContextSetTextMatrix (context, CGAffineTransformMake(1.0, 0.0, 0.0, -1.0, 0.0, 0.0));
Run the app, and the text should look all right this time:
Quartz 2D Part V Figure 2
Figure 2
Let’s try and understand the incantation that saved the day. The whole magic is in the six parameters, and I will only mention here those that are non-zero. You can read the detailed explanation of the logic in the documentation for CGAffineTransform.
So we are changing the system of coordinates in use, right? The six parameters describe the relationships between the old system and the new system. The very first parameter describes the relationship between the x coordinate of the new system and the x coordinate of the old system. We don’t want any changes here, so the value of the parameter is 1. The fourth parameter describes the relationship between the y coordinate of the new system and the y coordinate of the old system. We only need to reverse the direction, hence the value is –1.
As you can see, everything is very simple in this particular case. However, you are free to define a more complex transformation, to squeeze and shift the coordinates as you wish. For now, let’s better consider another useful transform. Here is the code:
CGContextSetTextMatrix(context, CGAffineTransformRotate(CGAffineTransformMake(1.0, 0.0, 0.0, -1.0, 0.0, 0.0), M_PI / 2));
Can you guess what it does? Replace the previous magical incantation with this one, et voila:
Quartz 2D Part V Figure 3
Figure 3
Well, I had to change the third parameter of CGContextShowTextAtPoint to 200, to make sure that the text remains within the graph boundaries. See, we can draw text vertically – can be quite useful for drawing labels.
To wrap it up, let’s do something for real, let’s number the data points of the line graph. Here is the code, it shouldn’t surprise you:
CGContextSetTextMatrix(context, CGAffineTransformMake(1.0, 0.0, 0.0, -1.0, 0.0, 0.0));
CGContextSelectFont(context, "Helvetica", 18, kCGEncodingMacRoman);
CGContextSetTextDrawingMode(context, kCGTextFill);
CGContextSetFillColorWithColor(context, [[UIColor colorWithRed:0 green:0 blue:0 alpha:1.0] CGColor]);
for (int i = 1; i < sizeof(data); i++)
{
NSString *theText = [NSString stringWithFormat:@"%d", i];
CGContextShowTextAtPoint(context, kOffsetX + i * kStepX, kGraphBottom - 5, [theText cStringUsingEncoding:NSUTF8StringEncoding], [theText length]);
}
And this is the result:
Quartz 2D Part V Figure 4
Figure 4
It looks okay, but it would be much better if the labels were centered under the data points, how do you think? To achieve that, we need to know the width of the label being drawn, and then make a correction to its x coordinate for a half of the label’s width.
Here is the line of code that will give us the size of the label. Obviously, we need to specify the same font that’s being used for drawing text:
CGSize labelSize = [theText sizeWithFont:[UIFont fontWithName:@"Helvetica" size:18]];
Finally, this is the amended line for actually drawing the label:
CGContextShowTextAtPoint(context, kOffsetX + i * kStepX - labelSize.width/2, kGraphBottom - 5, [theText cStringUsingEncoding:NSUTF8StringEncoding], [theText length]);
Run the app again, and now the labels will be nicely aligned under the data points:
Quartz 2D Part V Figure 5
Figure 5
As we said at the beginning of this series, 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 quickly. Now you can do everything that is needed to draw a bespoke graph of any complexity using Quartz 2D. The sky is the limit!

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.

没有评论:

发表评论