Recently I had to create an animating doughnut chart for a client, which would work well across devices, show percentages and take the least amount of time to implement. It's something that comes up 1,000 times when searching the interwebs, but they all seem to have some major flaw or are overly complicated.
The closest I got to finding one in the wild was this pen: http://codepen.io/joeyclouvel/pen/rDchL which, although not terrible, has a fair bit to be desired.
See the Pen Customisable Animated Donut Chart by Joey Clouvel (@joeyclouvel) on CodePen.
One of the main issues is that it uses
window.setInterval(), which is pretty oldskool and doesn't always play nicely— it should be using
Another issue is that it animates an SVG path's
d="" attribute directly, which is fine on some browsers but not on others (it basically destroys Safari). After the Safari-death scenario, I decided I would try and make it work with CSS transitions instead.
The magic in this new version comes from the SVG properties
stroke-dashoffset, which we'll abuse beyond their intended purpose, as well as
getComputedStyle. Everything else is just pretty bog-standard CSS/JS and is definitely not intended to be seen as the most beautifully written code :wink:
stroke-dasharray turns a stroke into an array of dashes (go figure!), with its value denoting the width of each dash. The property
stroke-dashoffset then says how many pixels we'll offset the dashes by.
If we set the
stroke-dasharray to the length of our path and modify the offset, we can quite easily create percentage width lines. When the offset is at 0, it's 100% wide. When the offset is at the width of the path, the line has been pushed out of the clipping area and appears invisible. If we now reverse animate between these two values, we get a line that moves from 0-100%.
See the Pen SVG line stroke-dashoffset animation by Matt Fletcher (@MaffooBristol) on CodePen.
This may not seem that impressive, but it becomes a lot cooler when we realise that we can run this on curves, circles and any other shape (as long as we can programatically work out the line's length, more about that in a second). See below the same code but on a circle rather than a straight path:
See the Pen SVG line stroke-dashoffset animation on circle by Matt Fletcher (@MaffooBristol) on CodePen.
This has worked pretty well, but has one flaw: the animation is meant to ease in and out, so it should slow to a stop when it reaches itself at the end of the circle. However, the stroke length and offset are waaaay too long for this shape- unroll the 100px radius circle and it's no longer 1000px wide, in fact it's 628.318530718px. That nice neat number is of course from primary school maths:
circumference = radius * pi * 2
Let's try it again but with both values at six-hundred-and-whatever:
See the Pen SVG line stroke-dashoffset animation on circle (adjusted) by Matt Fletcher (@MaffooBristol) on CodePen.
Much nicer! The next thing to do is make it so our doughnuts can animate between percentage values. To do this, we will have to use a simple formula to convert the % into the
offset = circumference - (circumference * percent / 100)
The following pen takes this formula and uses it to assign the new dashoffset. I have also added a
transform: rotate(90deg); and
transform-origin: 50%; to make sure the rotation starts from the top.
See the Pen SVG line stroke-dashoffset animation on circle (using percentages) by Matt Fletcher (@MaffooBristol) on CodePen.
Then we add in the static number with a bit more styling:
See the Pen SVG line stroke-dashoffset animation on circle (with static number) by Matt Fletcher (@MaffooBristol) on CodePen.
Finally, we want to actually update the text value based on the current position of the doughnut. The best way to do this is actually take the computed value of the animating SVG stroke, put it into the reverse of the above percent->offset calculation and then update it based on that. To make sure this runs smoothly, we will run this code in each
See the Pen SVG line stroke-dashoffset animation on circle (with working number) by Matt Fletcher (@MaffooBristol) on CodePen.
I'll probably update this blog post at some point, so apologies if it's a bit messy.
Lots of love,