Creating a Graph With Quartz 2D

Share this article

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.

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.

Frequently Asked Questions about Creating a Graph with Quartz 2D

What is Quartz 2D and why is it important in graphics imaging?

Quartz 2D is a powerful, high-level drawing engine provided by Apple for its macOS and iOS platforms. It’s part of the Core Graphics framework, which is used for handling image processing and drawing tasks. Quartz 2D is important in graphics imaging because it provides a wide range of features for creating complex graphics, including support for transparency, gradients, and anti-aliasing. It also supports PDF content, making it a versatile tool for developers working on graphics-intensive applications.

How does Quartz 2D compare to other graphics engines?

Quartz 2D stands out from other graphics engines due to its extensive feature set and its integration with the macOS and iOS platforms. It supports a wide range of image formats, including JPEG, PNG, and TIFF, and it also provides support for PDF content. This makes it a versatile tool for developers working on graphics-intensive applications. Additionally, Quartz 2D’s support for transparency, gradients, and anti-aliasing allows for the creation of complex and visually appealing graphics.

Can I use Quartz 2D for creating 3D graphics?

Quartz 2D, as the name suggests, is primarily designed for 2D graphics. It provides a wide range of features for creating complex 2D graphics, including support for transparency, gradients, and anti-aliasing. However, if you’re looking to create 3D graphics, you might want to consider using a 3D graphics engine like OpenGL or Metal, which are also provided by Apple for its macOS and iOS platforms.

How do I get started with Quartz 2D?

To get started with Quartz 2D, you’ll first need to have a basic understanding of the Swift or Objective-C programming languages, as these are the languages used for developing applications on the macOS and iOS platforms. Once you’re comfortable with one of these languages, you can start learning about the Core Graphics framework and Quartz 2D. Apple provides extensive documentation on these topics, which is a great resource for beginners.

What are some common use cases for Quartz 2D?

Quartz 2D is used in a wide range of applications, from simple graphics tasks to complex image processing. Some common use cases include creating custom views, drawing shapes and paths, rendering text, and handling images and PDF content. It’s also used for creating complex graphics with features like transparency, gradients, and anti-aliasing.

How does Quartz 2D handle colors and color spaces?

Quartz 2D provides extensive support for colors and color spaces. It supports both RGB and CMYK color spaces, and it allows for the creation of custom color spaces. Additionally, Quartz 2D provides a number of functions for manipulating colors, including functions for creating colors, converting between color spaces, and adjusting color components.

Can I use Quartz 2D for creating animations?

While Quartz 2D is primarily designed for static graphics, it can be used in conjunction with other frameworks to create animations. For example, you can use Quartz 2D to draw the individual frames of an animation, and then use the Core Animation framework to animate these frames.

How does Quartz 2D handle transparency?

Quartz 2D provides extensive support for transparency. It allows for the creation of transparent colors, and it provides a number of functions for manipulating the alpha channel of colors. Additionally, Quartz 2D supports blending modes, which can be used to create complex effects with transparency.

What are the performance considerations when using Quartz 2D?

While Quartz 2D is a powerful graphics engine, it’s important to keep performance in mind when using it. Complex graphics tasks can be resource-intensive, so it’s important to optimize your code as much as possible. This might involve using caching to reduce the amount of drawing that needs to be done, or using lower-level APIs for performance-critical tasks.

Can I use Quartz 2D for creating vector graphics?

Yes, Quartz 2D is well-suited for creating vector graphics. It provides a number of functions for drawing shapes and paths, and it supports features like gradients and anti-aliasing, which can be used to create complex vector graphics. Additionally, Quartz 2D’s support for PDF content makes it a great tool for working with vector-based PDF files.

Alexander KolesnikovAlexander Kolesnikov
View Author

Alexander wrote his first program in FORTRAN in the year 1979, in Java - in 1996, and also spent a number of years doing Web development with PHP. He is now a founder and a Director of Sirius Lab Ltd, a company offering solutions for iOS and Android platforms. You can also read his Sundraw blog.

iosios tutorialsQuartz 2DTutorials
Share this article
Read Next
Get the freshest news and resources for developers, designers and digital creators in your inbox each week