In this post I will breakdown how I was able to create an interactive Javascript pie chart using HTML Canvas.
In the example demonstrated below, we will have a pie chart that is dynamically created with randomized content. The script could be edited to hold static data as needed.
Javascript Interactive Pie Chart
Canvas
The DOM container holding the pie chart will be a Canvas element. Canvas elements are great UI tools to add to a UI. They can be used to manipulate the display of data as well as used in game graphic development in the browser.
Code
HTML
The HTML is very straightforward. Since this is Javascript driven, we will just need to add a DIV container to hold the Canvas element.
NOTE: I have updated a newer version of this to include a legend created from the dynamic data.
<!DOCTYPE html>
<html>
<head>
<title>Javascript</title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<style>
/* CSS styling */
</style>
<script>
//-----Javascript code
</script>
</head>
<body>
<button onclick="reloadPieChart()">Reload</button>
<div class="center">
<canvas id="piechartTest" width="450px" height="450px"></canvas>
<ul id="list">
<li><span> _ </span>One</li>
<li><span> _ </span>Two</li>
<li><span> _ </span>Three</li>
</ul>
</div>
</body>
</html>
CSS Styling
Basic CSS styling is used to center the main DIVÂ container with a Canvas element. Inside the <style>Â tag, just add the following.
html {
background-color: #004e5c;
}
body {
color: whitesmoke;
background-color: #004e5c;
display: grid;
width: 100%;
height: 100%;
margin: auto;
padding: 0;
}
.center {
display: inline-flex;
align-items: center;
width: 100%;
margin: auto;
justify-content: space-evenly;
}
canvas {
width: 50%;
display: inline;
aspect-ratio: 1 / 1;
}
ul {
width: 40%;
margin: 0;
padding: 0;
display: inline-block;
vertical-align: top;
list-style: none;
}
ul li {
padding: 15px;
}
ul li span {
background-color: red;
width: 25px;
color:rgba(255, 255, 255, 0);
box-shadow: 2px 2px black;
padding: 5px;
margin: 5px;
}
Javascript Functions
Breakdown Of Javascript Pie Chart Functions
This is where all the work is happening to create the Javascript pie chart. I will walk you through each of the functions with an explanation. At the bottom of page, there will be the full code of this example.
First part I will show is just global variable values used to create and store the data.
var total = 0; // used to store the total of each data value so we can create percentage
var mouseAngle = -1; // -1 means the user is not over canvas element
var data = []; // empty data array, in this example the data is randomized
var initAnimValues = {index: 0, angle: 0}; // this is used for startup animation
var dataLength = -1; // used to create randomized data in init()
var reload = false; // used to reload whole data set and animation on click
const names = ["Sarah", "Steve", "Al", "Beta", "Laurie", "Charles", "Sade", "Marley", "Everligh", "Carl", "Thomas", "Owen", "Gwendolyn", "Sophia", "Nicole", "Ava", "Chloe", "Vada", "Peter", "Destiny", "Maxine", "Scarlet", "Addy", "Anthony", "Tony"];//randomly chosen names for data array
Handle User Interactions
Now how to handle user interactions in the DOM. We will attach this action listener to the Canvas container.
Mousemove will calculate the angle in degrees the mouse is hovering over or the user touches on mobile. This is saved to a value named mouseAngle for later use to select a slice from the pie chart. Ensure to use the offsetY and offsetX to capture the relative position to parent. Otherwise you may get X and Y as an absolute coordinate.
Mousedown will trigger a switching boolean to reload the data array with new values and start the initAnim() again using new values.
Mouseout will set the mouseAngle to -1. This will reset the value so the animation will not try to use it.
var centerEvent = document.getElementById('piechartTest');
centerEvent.addEventListener(
"mousemove",
(event) => {
mouseAngle = calcAngleFromMouse(event.offsetY, event.offsetX, centerEvent.clientWidth/2);
},
false,
);
/* centerEvent.addEventListener(
"mousedown",
(event) => {
reload = true;
total = 0;
mouseAngle = -1;
data = [];
initAnimValues = {index: 0, angle: 0};
dataLength = -1;
init();
},
false,
); */
centerEvent.addEventListener(
"mouseout",
(event) => {
mouseAngle = -1;
},
false,
);
randomColor()
Simple Javascript script to create a random color returning a string value in HEX format.
function randomColor(){
return Math.floor(Math.random() * 0xffffff).toString(16);
}
randomNumber()
Simple Javascript script to return a random number. Math.random() returns a decimal value between 0 and 1. We are multiplying to create a larger number for use as the value attribute in each item of the data array.
function randomNumber(){
return Math.floor(Math.random() * 100);
}
calcPercent(value)
Generally, this function will get the percent of the currently drawn pie slice from the total of all the data values added together. This percent is used to then get the degrees this item will use in the pie chart.
function calcPercent(value){
var newVal = 360.0 * (value / total);
return newVal;
}
getPointOnCircle(centerX, centerY, radius, angleInDegrees)
The parameter values used will help determine the text placement when user moves mouse over pie slice. This will also shift the pie slice out of center a bit.
Center of Canvas
Center of Canvas
Distance from center you want to place the given drawn element.
The middle angle is used of each pie slice to ensure the drawn items are centered in middle of each pie slice.
function getPointOnCircle(centerX, centerY, radius, angleInDegrees) {
const angleInRadians = degreesToRadians(angleInDegrees);
const x = centerX + radius * Math.cos(angleInRadians);
const y = centerY + radius * Math.sin(angleInRadians);
return { x, y };
}
calcAngleFromMouse(y, x, center)
Calculates the degree of the circle the x and y coordinates of the mouse are located.
function calcAngleFromMouse(y, x, center){
var newVal = Math.atan2(y-center, x-center) * 180 / Math.PI;
//degree fix when angle is passed 180
// if angle is greater than 180, the value starts at -180 and counts up to 0
if(newVal < 0){
newVal = 180 - Math.abs(newVal) + 180;
}
return newVal;
}
degreesToRadians(degrees)
Takes the degrees and calculates the radians used to draw the pie slices.
function degreesToRadians(degrees) {
return degrees * (Math.PI / 180);
}
radiansToDegrees (radians)
Not used in this example but good to have in place while debugging. Calculates the degree from radians.
function radiansToDegrees (radians) {
return radians * (180/Math.PI);
}
reloadPieChart()
Reloads and rebuilds the data for the Javascript pie chart.
function reloadPieChart(){
reload = true;
total = 0;
mouseAngle = -1;
data = [];
initAnimValues = {index: 0, angle: 0};
dataLength = -1;
init();
}
init()
Starts the build and animation process. It builds the randomized dynamic data array of values to use for the Javascript pie chart. Then it will call the initial animation that happens only on startup.
function init(){
while(dataLength <= 1){
dataLength = Math.floor(Math.random() * 10);
}
data = [];
var list = document.getElementById("list");
list.innerHTML = "";
for(let j = 0; j < dataLength; j++){
var nameIndex = Math.floor(Math.random() * (names.length + 1))-1;
data.push(
{
value: randomNumber(),
color: "#" + randomColor(),
text:names[nameIndex >= 0 ? nameIndex : 0]
}
);
//data[j].text = data[j].value.toString();
var li = `<li><span style="background-color: ${data[j].color};"> _ </span>${data[j].value} - ${data[j].text}</li>`;
list.innerHTML += li;
total += data[j].value;
}
requestAnimationFrame(initAnim);
}
initAnim()
This animation basically follows each degree from 0 to 360 and builds the pie slices. When 360 is reached, the animation is done, the chartAnim() is called.
function initAnim(){
var lastAngle = 0;
var chart = document.getElementById('piechartTest');
var canvas = chart.getContext('2d');
const padding = 5;
const center = {x: chart.width/2, y: chart.height/2};
canvas.clearRect(0, 0, chart.width, chart.height);
canvas.shadowColor = "#000000";
canvas.shadowOffsetX = 1;
canvas.shadowOffsetY = 1;
for(let i = 0; i < data.length; i++){
if(i > initAnimValues.index){
break;
}
canvas.beginPath();
canvas.strokeStyle = "#000000";
canvas.lineWidth = 1;
var currentAngle = calcPercent(data[i].value);
canvas.fillStyle = data[i].color;
if(initAnimValues.index === i && initAnimValues.angle < currentAngle){
canvas.arc(center.x, center.y, center.x - padding * 4, degreesToRadians(lastAngle), degreesToRadians(initAnimValues.angle + lastAngle), false);
initAnimValues.angle++;
if(initAnimValues.angle >= currentAngle){
initAnimValues.index++;
initAnimValues.angle = 0;
}
} else {
canvas.arc(center.x, center.y, center.x - padding * 4, degreesToRadians(lastAngle), degreesToRadians(currentAngle + lastAngle), false);
}
canvas.lineTo(center.x, center.y);
canvas.closePath();
canvas.fill();
lastAngle+= currentAngle;
}
if(initAnimValues.index === data.length){
requestAnimationFrame(chartAnim);
} else {
setTimeout(initAnim, 1);
}
}
chartAnim()
Finally the last function that holds the constant animation. This will run continuously looking for mouse/touch movement and animate accordingly.
Full code for this main function is in jsFiddle below.
Hope this was useful.