How to draw a Dartboard using HTML’s Canvas

This post will cover the steps required to draw a traditional Dartboard using HTML5’s canvas.  The final product produces the following rendered board.

Dartboard
Dartboard drawn using HTML5’s canvas

To tackle this task one’s thought process was to picture how one would draw a dartboard using layers.  In theory you would start by drawing the black circle that forms the base of the board.  Followed by the alternating black and while pie sections.  The double and treble arcs would then be placed accordingly, followed by the outer-bull and bulls-eye circles.  And finally, the wire-works between the pie sections and lettering on the outer ring.

A working version of the above can be viewed Dart board Canvas. The HTML markup to create this page consists of the following:


<html lang="en">
 <head>
 <meta charset="utf-8" />
 <title>Simple Canvas Dartbaord</title>
 <script src='./victor.min.js'>
 
 
 
 <canvas id="canvas" width="400" height="400"></canvas>
 

The above code snip-it includes two important parts. This first part is the inclusion of the HTML5 canvas tag and the surrounding HTML elements.  This forms the foundation of the page.  The canvas tag is used to draw the board. The logic to draw the board is driven by the JavaScript code.  This is the second part. It can be found within the files included by the script tags e.g.

 <script src='./victor.min.js'>
 

The victor.js file brings is a mathematical library which provides a set of Vector based functions.  This is used to calculate the x and y positions of the various points of the board.  The second script provides the main logic for drawing onto the canvas.

The whole drawing processes is started when the first bit of JavaScript is executed. This can be found within ‘onload’ event of the page:

<body onload='DartBoard().init();'>

Drawing the black base of the board is fairly trivial. It is achieved using the arc function which is provided by the canvas API:

 function drawCircle(radius, colour, fillColour) {

 ctx.beginPath();
 ctx.arc(center.x, center.y, radius, 0, 2 * Math.PI, false);
 ctx.lineWidth = 1;
 ctx.strokeStyle = colour;

 if(fillColour) {
 ctx.fillStyle = fillColour;
 ctx.fill();
 }

 ctx.stroke();
 }

The code above is a utility function. It takes three parameters. The first is the radius of the circle. The second and third are the colour attributes. One for the outer border and the other for the fill circle. In this case the center point of the dartboard is fixed. This is set once when the draw function starts.

The base board, outer and inner rings (including the bulls-eye) all share the same center point. The only differing factor is the colour of the circles that make up the board and the radius of each.  Using the above utility function we first draw the base board (and later the inner and out bull).

baseboard

The next step is to draw the first pie segment. This is the segment representing the part which would score you twenty points.  To understand how we achieve this we first need to break the board down mathematically.  We know that the board is split into twenty pie-sliced shaped areas and a circle consists of three-hundred and sixty degrees.  Dividing three-hundred and sixty by twenty will give us the portion of the circle that one pie sliced takes:  a value of eighteen degrees.  The center of the first segment is positioned at zero degrees. We need to draw our first segment starting at three-hundred and sixty minus half the size of one segment (eight).  The end of the segment is this value plus eighteen.  Drawing this segment give us:

one_segment

The following code will draw all of the twenty segments:


for(counter = -9; counter < (360 - 9); counter += PIECES) {

buildRing(doubleTopMin, doubleTopMax,
counter, counter + PIECES,
count++ % 2 ? 'red' : '#03ff03');

buildRing(tripleMin, tripleMax,
counter, counter + PIECES,
count % 2 ? '#03ff03' : 'red');

buildRing(doubleTopMin, tripleMax,
counter, counter + PIECES,
count % 2 ? 'white' : 'black');

buildRing(tripleMin, twentyFiveTopMin,
counter, counter + PIECES,
count % 2 ? 'white' : 'black');

printLabel(counter, pos++);

}

This result in:
dartboard_without_wirework

As you can see from the above image the board is almost complete.  In the loop there are only two function calls.  The first is called ‘buildRing’ and the second ‘printLabel’.  The ‘buildring’ function again uses basic vector maths to calculate the coordinates (x and y) required to draw each of the segments.  Each segment consists of four sections.  Theses are the double top, upper fat, triple and lower parts.  The ‘buildring’ utility function takes the upper and lower bound of each and draws the segment using the colour supplied.  The code for this is:


function buildRing(min, max, startDeg, endDeg, colour) {
ctx.beginPath();
ctx.fillStyle = colour;
ctx.strokeStyle = colour;

for(var counter = startDeg; counter <= endDeg; counter += 1) { var doubleTopMaxPoint = centerVector .clone() .add(lengthVector .clone() .norm() .rotateDeg(counter) .multiply(new Victor(max, max)) ); ctx.lineTo(doubleTopMaxPoint.x, doubleTopMaxPoint.y); } for(counter = endDeg; counter >= startDeg; counter -= 1) {

var doubleTopMinPoint = centerVector
.clone()
.add(lengthVector
.clone()
.norm()
.rotateDeg(counter)
.multiply(new Victor(min, min))
);

ctx.lineTo(doubleTopMinPoint.x,
doubleTopMinPoint.y);
}

ctx.fill();
ctx.stroke();

}

This utility function loops around; moving one degree at a time from left to right along the top of the segment. Each point is passed to the lineTo function (a canvas API function).  It then repeats this in the second loop; but, goes right to left for the bottom of a segment.  At the end of this, the points calculated form a path.  This path is filled with the appropriate colour forming the full segment block.

The other utility function is the ‘printLabel’ function:


function printLabel(counter, pos)
{
var labels = [ 20, 1, 18, 4, 13, 6, 10, 15, 2,
17, 3, 19, 7, 16, 8, 11, 14, 9, 12, 5
];

var firstWireOfPeice = counter + 9;
var x = centerVector
.clone()
.add(lengthVector
.clone()
.norm()
.rotateDeg(firstWireOfPeice)
.multiply(new Victor(doubleTopMax, doubleTopMax))
);

var adjust;

if (firstWireOfPeice < 55 || firstWireOfPeice > 295) {
adjust = new Victor(radius - 10, radius - 10);
} else if (firstWireOfPeice > 95 && firstWireOfPeice < 265) {
adjust = new Victor(radius + 10, radius + 10);
} else {
adjust = new Victor(radius, radius);
}

var edge = centerVector
.clone()
.add(lengthVector
.clone()
.norm()
.rotateDeg(firstWireOfPeice)
.multiply(adjust)
);

drawLabel(labels[pos], edge, x);

}

This function begins by declaring an array of scores. The parameters passed into the function represents the index (or counter) of the label to draw and the position in degrees of the start of the segment. Again using a simple bit of vector math the labels position is calculated. This position is then used to draw the label.

The penultimate part of the code is the drawing of the metal wire-works which divide each segment.  This is done within the ‘drawRadials’ function:

 function drawRadials() {

 ctx.lineWidth = 2;
 ctx.strokeStyle = 'white';

 for(counter = 9; counter < 360; counter += PIECES) {

 var offSet = new Victor(0, 10);

 x = centerVector
 .clone() 
 .add(lengthVector
 .clone() 
 .norm()
 .rotateDeg(counter)
 .multiply(new Victor(doubleTopMax, doubleTopMax))
 );

 drawRadial(x);
 }

 function drawRadials() {

 ctx.lineWidth = 2;
 ctx.strokeStyle = 'white';

 for(counter = 9; counter < 360; counter += PIECES) {

 var offSet = new Victor(0, 10);

 x = centerVector
 .clone() 
 .add(lengthVector
 .clone() 
 .norm()
 .rotateDeg(counter)
 .multiply(new Victor(doubleTopMax, doubleTopMax))
 );

 drawRadial(x);
 }
 }

 }</pre>
<pre>

The first function ‘drawRadial’ simply repeats the loop discussed above. However, this time we simply draw a silver line from the center of the board to the outer limit of the double ring. Drawn on it own produces the following:

dartboard-radials

And finally, we draw the inner and outer bull area. I left this until last as it overlays the center area and thus, hides the parts of the silver radials that we do not want to see. The inner, outer bull and its wire-work are drawn using the following:

 drawCircle(doubleTopMax, 'silver');
 drawCircle(doubleTopMin, 'silver');
 drawCircle(tripleMax, 'silver');
 drawCircle(tripleMin, 'silver');
 drawCircle(twentyFiveTopMin, 'silver', 'green');
 drawCircle(bullTopMin, 'silver', 'red');


As you can see this simply calls the ‘drawCircle’ function. Passing in the radius at which the circles should be drawn and their appropriate colours. This result in the final render the board:

Dartboard
Dartboard drawn using HTML5’s canvas

 

That concludes one’s experiment with sport :-). I hope this helps someone out there. A working version of the above can be viewed HTML5 Canvas dartboard. If you have any feedback please comment below.  This is also available at my git repository HTML5 goodies.

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s