Introduction
The purpose of this tip is to illustrate circular menu using HTML5 Canvas. Before HTML 5, we used screen position and images to represent a circular menu, and probably will need more work to create the one shown below.

The code from this tip works only with latest browsers (Tested using Internet Explorer 11 and Chrome).
Background
This article is inspired based on another article I had written some time back using WPF technology.
To understand HTML5 Canvas ARC function, please visit the below link:
Using the Code
The structure of the code is as below:
- HTML file - This file contains the
canvas
element - circularmenu.js - Necessary logic to create menu
- JQuery library
Starting with HTML, we need "canvas
" element wrapped in a "div
" as shown below.
<div id="popUp"
style="position:absolute;display:none;width:300px;height:300px;z-index:9000">
<canvas id="myCanvas" width="150" height="150">
</canvas>
</div>
Once we have canvas
, we can hook up menu to any HTML element's event using JavaScript as shown below. In this example, I used click event of element (id=showround
).
<span id="showRound" style="cursor:pointer">Circular Menu</span>
$(function () {
var dataSource = [ {'key': '1', 'value': 'File', 'color' : 'red'},
{'key': '2', 'value': 'Open', 'color': 'green'},
{'key': '3', 'value': 'Edit', 'color': 'blue'},
{'key': '4', 'value': 'Save', 'color': 'brown'}
];
var pie = new Pie.Menu('popUp', dataSource); //Create the menu object
pie.click(function (data) { //Hooking the callback function during click event
alert(data.key + ' => ' + data.value);
});
$('#showRound').on("click", function (event) {
pie.showMenu(event);
})
}
);
In the above function, I am creating a "Pie.Menu
" object and passing 2 parameters to the its constructor, i.e. passing "div
" tag container that holds canvas
and array of data. Each data object should have key, value and color, which will be used by the "Pie.Menu
" object. Once we have that object, assgin a callback function which will be invoked when the user clicks on Arc.
Let us get into the details of "Pie.Menu
" which resides in circularmenu.js. It has two major functions:
measure
- Does the necessary calculations draw
- Does the actual draw.The above functions will be called during click event of "showRound
"
What "measure" does?
This fuction does all the math to determine arc measurment required for drawing. For example, say if the datasource (passed in constructor) has 4 objects. Then each Arc angle is 360 / 4 = 90. Once we have Arc angle, then start and end point of each arc can be determined as below:
for (var i = 0 ; i < count; i++) {
var dSource = this.dataSource[i];
var sAngleDeg = angle * i;
var eAngleDeg = angle * (i + 1);
var sAngleRad = sAngleDeg * Math.PI / 180;
var endAngleRad = eAngleDeg * Math.PI / 180;
var tArc = new Pie.Arc(sAngleRad, endAngleRad, dSource.key, dSource.color);
tArc.arcText = generateArcText(sAngleDeg, eAngleDeg, dSource.value, this.radius, this.thickness, this.width / 2);
this.arcs.push(tArc);
}
"Pie.Arc
" is a helper object to hold the Arc
information. "Pie.Menu
" has array property to hold each Arc
.
"Pie.ArcText
" is a helper object to hold the Text
information. "Pie.Arc
" has array property to hold each text.
What "draw" function does?
This function iterates through the "Pie.Arc
" array and draws the arc as shown below on the canvas.
for (var i = 0; i < this.arcs.length; i++) {
var tArc = this.arcs[i];
ctx.beginPath();
ctx.arc(this.width / 2, this.height / 2, this.radius, tArc.sAngle, tArc.eAngle);
ctx.lineWidth = this.thickness;
ctx.strokeStyle = tArc.color;
ctx.stroke();
ctx.closePath();
drawText(ctx, tArc);
}
ARC click functionality
Canvas
element can listen to click event which further invokes the callback function we registerd with "Pie.Menu
" object. However, we need to restrict the triggering of that function when mouse is on Arc
and shouldn't fire outside Arc
. This is achieved by converting the event points to Angle
using in built function "Math.atan2
" and comparing with stored angles of each arc as shown below:
var pos = findPos(this);
var x = event.pageX - pos.x;
var y = event.pageY - pos.y;
var vX = x - pie.center;
var vY = y - pie.center;
var tR = Math.sqrt(vX * vX + vY * vY)
if (tR >= 60 - pie.thickness / 2 && tR <= 60 + pie.thickness / 2) {
var degRad = Math.atan2(vY, vX);
if (degRad < 0) {
degRad = 2 * Math.PI + degRad;
}
for (var i = 0; i < pie.arcs.length; i++) {
var arc = pie.arcs[i];
var tmpStartRad = arc.sAngle;
var tmpEndRad = arc.eAngle;
if (tmpStartRad < 0)
tmpStartRad = 2 * Math.PI + tmpStartRad;
if (tmpEndRad < 0)
tmpEndRad = 2 * Math.PI + tmpEndRad;
if (degRad >= tmpStartRad && degRad <= tmpEndRad) {
pie.callback(pie.dataSource[i]);
break;
}
}
}
Finally, I hooked up the canvas "mouse move" to change the cursor to hand when the user hovers over the Arc
.