Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / web / HTML

Circular Menu using HTML 5 Canvas

4.59/5 (6 votes)
25 Oct 2014CPOL2 min read 22.8K   305  
Circular Menu using HTML 5 Canvas

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:
  1. HTML file - This file contains the canvas element
  2. circularmenu.js - Necessary logic to create menu
  3. JQuery library

Starting with HTML, we need "canvas" element wrapped in a "div" as shown below.

HTML
 <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).

HTML
<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:

  1. measure - Does the necessary calculations
  2. 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:

JavaScript
for (var i = 0 ; i < count; i++) {
                    var dSource = this.dataSource[i];
                    var sAngleDeg = angle * i; //Angle of Arc's starting point in Deg
                    var eAngleDeg = angle * (i + 1); //Angle of Arcs's end point in Deg
                    var sAngleRad = sAngleDeg * Math.PI / 180; //Angle of Arc's starting point in Radian
                    var endAngleRad = eAngleDeg * Math.PI / 180; //Angle of Arcs's end point in Radian
                    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.

JavaScript
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:

JavaScript
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) //r * r = x * x + y * y
                if (tR >= 60 - pie.thickness / 2 && tR <= 60 + pie.thickness / 2) {
                    var degRad = Math.atan2(vY, vX); //Convert point to angle
                    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) {
                            //alert(pie.dataSource[i].value);
                            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.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)