Create animated donut chart using SVG and javascript

Created on

05 Feb 2019

Updated on

20 Jan 2021

There are many plugins to create awesome charts using SVG or canvas. But if all you want is a simple and beautiful doughnut chart that can animate, display information and be interactive, then this post is for you.

I will be using SVG to create the doughnut shape as event handling is easier on SVG than canvas.

I will show two methods to create the effect. One is to manually create the elements and the other is to use js to create and fill the elements.

Method 1

Using circle elements to create doughnut charts.

The below code is for creating a static doughnut chart with 4 items.

<div class="doughnut">
    <svg width="100%" height="100%" viewBox="0 0 100 100">
        <circle cx="50" cy="50" r="30" stroke="#80e080" stroke-width="15" fill="transparent" stroke-dasharray="188.496" stroke-dashoffset="141.372"  transform='rotate(-90 50 50)'/>
		<circle cx="50" cy="50" r="30" stroke="#4fc3f7" stroke-width="15" fill="transparent" stroke-dasharray="188.496" stroke-dashoffset="103.6728"  transform='rotate(0 50 50)'/>
		<circle cx="50" cy="50" r="30" stroke="#9575cd" stroke-width="15" fill="transparent" stroke-dasharray="188.496" stroke-dashoffset="169.6464"  transform='rotate(162 50 50)'/>
		<circle cx="50" cy="50" r="30" stroke="#f06292" stroke-width="15" fill="transparent" stroke-dasharray="188.496" stroke-dashoffset="150.7968"  transform='rotate(198 50 50)'/>
	</svg>
</div>

Which will produce the below output.

So how does it work?

We have four circle elements, each representing a part of the data to be shown. And each of these circle elements has a stroke-dasharray equal to its perimeter, which can be found out by using the formula 2 * π * r. In this case, the perimeter is 188.496.

The fill is set to transparent and a suitable stroke (color) is applied. stroke-width property is used to set the width of the doughnuts.

To fill each part of the doughnut to its respective amount, the stroke-dashoffset property is used. To find the required amount of stroke-dashoffset the below formula is used.

stroke-dashoffset = perimeter - perimeter * amount / 100.

Where amount is the amount to be filled in percentage.

Now we need to rotate each element such that they will start from the previous element’s end transform.

The value for rotation can be calculated using the formula below.

rotation = previousAmount * 360 / 100

Where previousAmount is the amount filled in the percentage of the previous element, 0 for the first element.

The second and third value in the transform is the start coordinate of the rotation. 50,50 will set it to the center.

In my example I used -90 as the base rotation instead of 0 because i like the fill to start at the top instead of the right side.
To do this, simply subtract 90 from the above equation.

Method 2

Use javascript to dynamically add the circle elements and automatically fill the values.

This method is more useful if you need to create a chart from unknown values or if you have too much data to be created manually.

Start with a div#doughnut

<div id="doughnut"></div>

We need only one element since all the other elements are added dynamically.

And in JS,

var data = [{
    fill:15,
    color:"#80e080"
},{
    fill:35,
    color:"#4fc3f7"
},{
    fill:20,
    color:"#9575cd"
},{
    fill:30,
    color:"#f06292"
}]

This is just a basic format for the data, it can be different from the above or contain more info.

Now we need to create and append the svg element to the container element.

var doughnut = document.querySelector("#doughnut");
var svg = document.createElement("svg");
svg.setAttribute("width","100%");
svg.setAttribute("height","100%");
svg.setAttribute("viewBox","0 0 100 100");
doughnut.appendChild(svg);

To create each part of the doughnut, the data object is utilized. We will create the required number of circle elements with proper color and fill amount and then append them to the svg .

var data = [{
    fill:15,
    color:"#80e080"
},{
    fill:35,
    color:"#4fc3f7"
},{
    fill:20,
    color:"#9575cd"
},{
    fill:30,
    color:"#f06292"
}]
var doughnut = document.querySelector("#doughnut"),
svg = document.createElementNS("http://www.w3.org/2000/svg", "svg"),
filled = 0;
svg.setAttribute("width","100%");
svg.setAttribute("height","100%");
svg.setAttribute("viewBox","0 0 100 100");
doughnut.appendChild(svg);
data.forEach(function(o,i){
	var circle = document.createElementNS("http://www.w3.org/2000/svg","circle"),
	startAngle = -90,
	radius = 30,
	cx = 50,
	cy = 50,
	strokeWidth = 15,
	dashArray = 2*Math.PI*radius,
	dashOffset = dashArray - (dashArray * o.fill / 100),
	angle = (filled * 360 / 100) + startAngle;
	circle.setAttribute("r",radius);
	circle.setAttribute("cx",cx);
	circle.setAttribute("cy",cy);
	circle.setAttribute("fill","transparent");
	circle.setAttribute("stroke",o.color);
	circle.setAttribute("stroke-width",strokeWidth);
	circle.setAttribute("stroke-dasharray",dashArray);
	circle.setAttribute("stroke-dashoffset",dashOffset);
	circle.setAttribute("transform","rotate("+(angle)+" "+cx+" "+cy+")");
	svg.appendChild(circle);
	filled+= o.fill;
})

Live demo:

Fiddle link.

Adding animations.

For creating animations I will use the transition property. We first need to determine the total duration of the animation and then the duration is distributed among the circles with respect to their fill amount.

In order to create the animation effect, the stroke-dashoffset is initially set to the same as stroke-dasharray then the transition is added and stroke-dashoffset is set to its respective position.

var data = [{
    fill:15,
    color:"#80e080"
},{
    fill:35,
    color:"#4fc3f7"
},{
    fill:20,
    color:"#9575cd"
},{
    fill:30,
    color:"#f06292"
}]
var doughnut = document.querySelector("#doughnut"),
svg = document.createElementNS("http://www.w3.org/2000/svg", "svg"),
filled = 0;
svg.setAttribute("width","100%");
svg.setAttribute("height","100%");
svg.setAttribute("viewBox","0 0 100 100");
doughnut.appendChild(svg);
data.forEach(function(o,i){
	var circle = document.createElementNS("http://www.w3.org/2000/svg","circle"),
	startAngle = -90,
	radius = 30,
	cx = 50,
	cy = 50,
	animationDuration = 2000,
	strokeWidth = 15,
	dashArray = 2*Math.PI*radius,
	dashOffset = dashArray - (dashArray * o.fill / 100),
	angle = (filled * 360 / 100) + startAngle,
	currentDuration = animationDuration * o.fill / 100,
	delay = animationDuration * filled / 100;
	circle.setAttribute("r",radius);
	circle.setAttribute("cx",cx);
	circle.setAttribute("cy",cy);
	circle.setAttribute("fill","transparent");
	circle.setAttribute("stroke",o.color);
	circle.setAttribute("stroke-width",strokeWidth);
	circle.setAttribute("stroke-dasharray",dashArray);
	circle.setAttribute("stroke-dashoffset",dashArray);
	circle.style.transition = "stroke-dashoffset "+currentDuration+"ms linear "+delay+"ms";
	circle.setAttribute("transform","rotate("+(angle)+" "+cx+" "+cy+")");
	svg.appendChild(circle);
	filled+= o.fill;
	setTimeout(function(){
		circle.style["stroke-dashoffset"] = dashOffset;
	},100);
})

The above shown is the entire code required to create an animated doughnut chart.

We declare the duration of the animation in the variable animationDuration and it is divided and distributed among each element.

After the element is appended, stroke-dashoffset is changed to its respective amount. Note that a setAnimation() function is used to make sure that the elements are appended and ready.

A live demo is shown below, you can also check this fiddle.

Do leave a comment if you have any doubts, suggestions or improvements.

Comments

Post a comment

This site is protected by reCAPTCHA and the Google Privacy Policy and Terms of Service apply.