Slice 1clock comment github-circled instagramm lastfm pocket tags torso twitter

vɛ:berʃen

Animating SVG Pie Chart

No libraries please

A few months (I know, I know) ago Anselm Hanneman asked for a solution to create interactive pie charts but without including a lot of KBs of Javascript.

At the same time I came across a similar task to create a SVG pie chart and so I provided Anselm with my starting point which he eventually used to create a chart on the site of the WDRL's website.

While Anselm created a pie chart for his own purpose, I had to create a pie chart for a client's project in which the pie charts should be animated.

SMIL to rescue?

I quickly remembered an article of fellow Batman-Geek Stefan in which he described basic tweening in SVG with the help of SMIL.

So I jumped right in and tried to implement the animation using SMIL. I used the original javascript file of Anselm as foundation. Unfortunately the outcome was not as I expected:

See the Pen VYjdXy by Robert Weber (@closingtag) on CodePen.

I did not know what was going wrong here and so I asked Stefan if he knows what the problem is.

Unfortunately the browser does not know how to calculate between the to paths or does not know how I want him to calculate between the two paths.

In detail only one point changes from:

M 100,100 L 100,0 A 100,100 0 0 1 100,0 Z

To:

M 100,100 L 100,0 A 100,100 0 0 1 193.77521321470806,65.26947471551797 Z

I want the browser to draw an arc between the two points but it is drawn as a straight line.

requestAnimationFrame to rescue!

After the whole SMIL disappointment I had to find another way to animate the pie chart. Because I used requestAnimationFrame for whole bunch of other stuff recently, I started to implement it into the pie chart script.

If you do not know how requestAnimationFrame works, Paul Irish wrote an article four(!) years ago that explain the basic concepts.

So where do we start? First of all we need to specify a duration for our animation. Lets say 3 seconds. Then we need to determine the interval angle in which we can update the pie chart. 60 Frames per second is pretty decent. As SVG is working with radian measures and not with degrees we need to calculate with Π:

var duration = 3000;
var steps = Math.PI * 2 / Math.floor( duration / 60 );

To prepare the drawing of the slices we create an array for the slices which holds a <path /> element for each slice. We use the main for loop to fill the array.

var paths = [];
...
for (i = 0; i < data.length; i++) {
    var path = document.createElementNS(svgns, 'path');

    path.setAttribute('fill', colors[i]);
    paths.push(path);

    chart.appendChild(path);
}
...     

We create a helper function which draws a single slice. We can use the math from the main for loop as it stays the same.

    var drawSlice = function(path, startangle, endangle) {

        var big = endangle - startangle > Math.PI ? 1 : 0;
        var x1 = cx + r * Math.sin(startangle);
        var y1 = cy - r * Math.cos(startangle);
        var x2 = cx + r * Math.sin(endangle);
        var y2 = cy - r * Math.cos(endangle);

        var d = 'M ' + cx + ',' + cy + ' L ' + x1 + ',' + y1 + ' A ' + r + ',' + r + ' 0 ' + big + ' 1 ' +  x2 + ',' + y2 + ' Z';

        path.setAttribute('d', d);
    };    

Now we define a function which is called by requestAnimationFrame. In this function we start with an angle of 0 and increase this angle with our calculated interval angle and draw a slice. We also make sure that we switch the index of the paths array we declared earlier. Then we call requestAnimationFrame again.

    var loop = function() {

        // check if we are still in the same slice
        if ( i < endangle ) {

            // increase by angle interval
            i = i + steps;

            // make sure we do not draw over the endangle of the slice
            drawSlice( paths[pathIndex], startangle, startangle + i > startangle + endangle ? startangle + endangle : startangle + i)   

            // call requestAnimationFrame again
            window.requestAnimationFrame(loop);

        } else { // take the next slice of pie chart

            startangle = startangle + endangle;

            if ( pathIndex + 1 !== paths.length ) {

                // set indices and angles
                pathIndex++;
                i = 0;
                endangle = angles[pathIndex];

                // call requestAnimationFrame again
                window.requestAnimationFrame(loop);

            }
        }
    };

Then we have to make sure that we call requestAnimationFrame for the first time.

    i = 0;
    var endangle = angles[0];
    window.requestAnimationFrame(loop);

And thats it. Here you can see the end result:

See the Pen ogpNXq by Robert Weber (@closingtag) on CodePen.

Further reading: