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