Create A Javascript Pie Chart Using HTML Canvas
Creating an interactive Javascript pie chart using a HTML canvas to improve your data presentations on your webpage.

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.

NOTE: I have update code to move mousedown to a button click to reload. Otherwise mobile touches will just reload, not animate data slices.
mousemove

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

Mousedown will trigger a switching boolean to reload the data array with new values and start the initAnim() again using new values.

mouseout


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.

centerX


Center of Canvas

centerY


Center of Canvas

radius

Distance from center you want to place the given drawn element.

angleInDegrees

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.

During this calculation, if the degree passes 180, the value flops to -180 as the output. Ex. 181 actually reads as -179. The if statement accounts for the change so the value will spit out as a positive value between 0 and 360.
	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.