Rotating a Polygon: Drawing a Directional Arrow

I needed to draw an arrowhead to show the direction of lines drawn on the canvas. When I searched for this I didn’t find any complete solutions, so I wanted to share mine. Includes useful canvas routines to translate and rotate a data structure representing the polygon coordinates.

I’ve been plugging away at <canvas> drawing and it’s been pretty exciting. I’ve had some great success with my flowchart interface so far, I’ll be posting a demo of that here soon. One thing I ran up against was needing to draw an arrowhead to show the direction of lines connecting two boxes. When I searched for this I didn’t find any complete solutions, so I wanted to share mine.

Note that canvas support is available natively in basically every browser except MSIE (even IE8). Althought IE does not support <canvas> natively, support is available through a project called explorercanvas. The demos here have not implemented explorercanvas, so it will only work with every other browser than IE. I’d like to see this working in IE with explorercanvas, but I haven’t had time to play with that yet. (I assume it’s easy.)

Drawing on a canvas is relatively simple using only pure Javascript. Shapes on the canvas are drawn by creating a path and filling it. To define the points for our arrow, we’ll create an array of arrays, each defining an x,y point for our arrow. You’ll notice that the points create a right-pointing arrow, this is important. It will be drawn with 0,0 corresponding to the end of the line. Here’s my arrow:

var arrow = [
    [ 2, 0 ],
    [ -10, -4 ],
    [ -10, 4]
];

You can add as many coordinates as you want, to make more complex shapes. The last point will connect back to the first. Let’s look at a simple function for drawing a filled polygon from this type of data structure.

function drawFilledPolygon(shape) {
    ctx.beginPath();
    ctx.moveTo(shape[0][0],shape[0][1]);

    for(p in shape)
        if (p > 0) ctx.lineTo(shape[p][0],shape[p][1]);

    ctx.lineTo(shape[0][0],shape[0][1]);
    ctx.fill();
};

(Observe, as per the way filling a shape works with the canvas, the first coordinate set—index zero—is not drawn in the loop but is used to position the pen and close the polygon to that point before filling it.)

If we were to pass the shape to this function now, it would draw it. However, it would be drawn off the canvas since it points to 0,0 from the left (the points would be e.g. -10,4). This doesn’t cause an error, but it has no effect on the visible space. To draw the arrow at the location and angle we want, we’ll create a few functions to translate the shape before we pass it to our drawFilledPolygon() function.

We need to move the shape to the point where we want to draw it. This is done simply by offsetting the coordinates. This function will be similar to the others in that it will loop over the shape data and return a new shape with adjusted coordinates.

function translateShape(shape,x,y) {
    var rv = [];
    for(p in shape)
        rv.push([ shape[p][0] + x, shape[p][1] + y ]);
    return rv;
};

We’re going to apply the same technique to rotation by angle. We will also define a function for the trigonometry to actually rotate the points.

function rotateShape(shape,ang) {
    var rv = [];
    for(p in shape)
        rv.push(rotatePoint(ang,shape[p][0],shape[p][1]));
    return rv;
};
function rotatePoint(ang,x,y) {
    return [
        (x * Math.cos(ang)) - (y * Math.sin(ang)),
        (x * Math.sin(ang)) + (y * Math.cos(ang))
    ];
};

Bringing this all together is a function to draw the line and the rotated arrow at it’s end:

function drawLineArrow(x1,y1,x2,y2) {
    ctx.beginPath();
    ctx.moveTo(x1,y1);
    ctx.lineTo(x2,y2);
    ctx.stroke();
    var ang = Math.atan2(y2-y1,x2-x1);
    drawFilledPolygon(translateShape(rotateShape(arrow,ang),x2,y2));
};

Notice that Math.atan2() is called to calculate the angle. The shape and angle are passed through rotateShape() and translateShape() before being drawn by drawFilledPolygon(). This should be easy to visualize after studying the functions themselves. You could also write other functions to manipulate the shape before drawing.

With these functions and an arrow shape defined, invoke the canvas and draw some lines. First, insert a <canvas> tag into your HTML somewhere, give it a size and an id:

<canvas width="600" height="400" id="drawing"></canvas>

Normally I’d wrap the initialization code in jQuery’s DOM-ready wrapper, but since that would be serious overkill (loading jQuery just to use that wrapper)—and because I want to keep this pure Javascript—I will define an init function and add it to window.onload. Still unobtrusive, and easy enough if you only have one function to call.

var canvas,ctx;
var cw = 600, ch = 400, na = 35;

function initArrows() {
    canvas = document.getElementById('drawing');
    ctx = canvas.getContext('2d');
    randomLines();
};
function randomLines()
{
    ctx.clearRect(0,0,cw,ch);
    for(i = 0; i < na; i++)
        drawLineArrow(
            Math.random() * cw, Math.random() * ch,
            Math.random() * cw, Math.random() * ch
        );
    return false;
};

window.onload = initArrows;

The completed example looks like this. As you can see above, the code just draws some random arrows, to demonstrate the functionality on the canvas.

Draggable Arrows demo. Taking this a step closer to a user interface, I’ve tied in some simple mouse events to redraw the arrow when you press the button and drag in the canvas area. The appearance of dragging is created by constantly redrawing the canvas as the mouse moves. To create an interface that moves on top of other content, use CSS to position the canvas over the desired area of the page (even another canvas).

Tags: , ,

One Response to “Rotating a Polygon: Drawing a Directional Arrow”

  1. StruschieNo Gravatar wrote:

    that is really cool and usefull – thank U!