Mac/Macintosh educational/tutor software developer

Faraday R&D

| Home | Source Code |

Mac Developer (Australia): Mac Example -- Drawing Arrows with Cocoa

Source code

A source code example of how to use Cocoa to to draw arrows. This is a very simple application of secondary school geometry and vectors to calculate the endpoints of the various line segments. No trigonometry is used.

An XCode project implementing this algorithm can be downloaded here...

Theory

The "shaft" of the arrow is just a simple line segment defined by its end points whose coordinates are (x0, y0) and (x1, y1). The x and y displacements of the line segment are (Dx, Dy) = (x1 - x0, y1 - y0). The length of the arrow is l (l = sqrt(Dx.Dx + Dy.Dy) but that's not relevant here).


Arrow diagram 1

The axis length of the "arrow head" is define to be lh. The base of the arrow head is located at (xb, yb) and can be found from the formula (xb, yb) = (x1 - Dx.lh/l, y1 - Dy.lh/l). This is just a simple application of similar triangles. See Diagram 2.

Arrow diagram 2
Arrow diagram 2

The "wing tips" of the arrow head are located at (xw1, yw1) and (xw2, yw2). These coordinates can be found by scaling the arrow head vector by a ratio (k -- normally between 0 and 2) and then rotating the resulting vector by positive 90 degrees for one wing and negative 90 degrees for the other wing. The scaled arrow head vector is: (k.Dx.lh/l, k.Dy.lh/l), so the coordinates can be found from the formulas: (xw1, yw1) = (xb - k.Dy.lh/l, yb + k.Dx.lh/l) and (xw2, yw2) = (xb + k.Dy.lh/l, yb - k.Dx.lh/l). These formulas arise by recognising that rotating a vector by a positive right angle swaps the x and y displacement values and inverts the sign of the y displacement. Similarly rotating a vector by a negative right angle swaps the x and y displacement values and inverts the sign of the x displacement. See Diagram 3 which shows the various steps.

The source code that implements this follows:

/*
 ----------------------------------------------------
 The drawing is done here when requested by the
 Cocoa framework. This is where the arrow drawing algorithm
 is implemented.
 */
- (void)drawRect:(NSRect)rect
{
    NSPoint arrowOrigin;
    NSPoint arrowTip;
    NSPoint arrowHeadBase;
    NSPoint arrowHeadWing1;
    NSPoint arrowHeadWing2;
    float deltaX = 0.0;
    float deltaY = 0.0;
    
    float deltaXBase = 0.0;
    float deltaYBase = 0.0;

    float headToShaftRatio = 0.0;
    float deltaXWing = 0.0;
    float deltaYWing = 0.0;
    
    NSRect bounds = [self bounds];
    
    // Draw the background
    [mBackgroundColor set];
    [NSBezierPath fillRect: bounds];

    // Set the arrow colour
    [mArrowColor set];

    // The arrow origin will be at the center of the view
    arrowOrigin.x = bounds.size.width/2;
    arrowOrigin.y = bounds.size.height/2;
    
    // Create the path that will contain the arrow drawing instructions
    // and begin drawing.
    NSBezierPath* arrowPath = [NSBezierPath bezierPath];
    [arrowPath setLineWidth: mLineWidth];
    [arrowPath moveToPoint: arrowOrigin];
    
    // Calculate the arrow tip location from the polar coordinates
    deltaX = mArrowLength*cos(mArrowAngle*DEGREES_TO_RADIANS);  // Radians
    deltaY = mArrowLength*sin(mArrowAngle*DEGREES_TO_RADIANS);
    arrowTip.x = arrowOrigin.x + deltaX;
    arrowTip.y = arrowOrigin.y + deltaY;
    
    // Define the arrow shaft
    [arrowPath lineToPoint: arrowTip];
    
    // Calculate the location of the base of the arrow head
    headToShaftRatio = mArrowHeadLength/mArrowLength;
    deltaXBase = headToShaftRatio*deltaX;
    deltaYBase = headToShaftRatio*deltaY;
    arrowHeadBase.x = arrowTip.x - deltaXBase;  
    arrowHeadBase.y = arrowTip.y - deltaYBase;  
    
    // Calculate the wing tips of the arrow head
    deltaXWing = mArrowHeadAspectRatio*deltaXBase;
    deltaYWing = mArrowHeadAspectRatio*deltaYBase;
    arrowHeadWing1.x = arrowHeadBase.x - deltaYWing ;   
    arrowHeadWing1.y = arrowHeadBase.y + deltaXWing ;   
    arrowHeadWing2.x = arrowHeadBase.x + deltaYWing ;   
    arrowHeadWing2.y = arrowHeadBase.y - deltaXWing ;   
    
    // Define the arrow head wings
    [arrowPath moveToPoint: arrowTip];
    [arrowPath lineToPoint: arrowHeadWing1];
    [arrowPath moveToPoint: arrowTip];
    [arrowPath lineToPoint: arrowHeadWing2];
    
    // Draw the arrow
    [arrowPath stroke];
    
}
/*
 ----------------------------------------------------
 */
	  

Comments or corrections welcome. Send an email to farad'aty'farad.com.au.

An XCode project implementing this algorithm can be downloaded here...

| Copyright © 2004, Faraday R&D. | Feedback |