Creating a donut chart using SVG

Create a lightweight donut chart with svg using plain javascript.

Donut charts are good replacements for the standard pie charts. It has an elegant feel to it. It's even more suitable when multiple pie charts overburden the visuals with its weight.

Charts and data visualisation are better off left to external libraries like the popular D3.js and Chart.js. However, if the project is small and charts are limited you might as well make charts manually to decrease the bundle size. Two common ways of doing that would be using canvas and using SVG. Unlike canvas charts, SVG charts are scalable, crisp on the visuals and interactable easily.

Lets create a simple circular ring (not an actual circle).

<svg viewBox="0 0 48 48">
    <path d="M24,4 a20,20 0 0,1 0,40 a20,20 0 0,1 0,-40 z" fill="none" stroke="#144E7F" stroke-width="8" />
</svg>

We need to show stroke length proportional to the data which can be done using getTotalLength() function. It returns the total length of the path. Now if we assign ${strokeLength}, ${totalLength - strokeLength} to the stroke-dasharray property, this creates the stroke of paticular length and leaves the rest of the gap empty.

It should be noted that stroke-dasharray property can take pair value as well. stroke-dasharray: x y; x is the length of dash and y is the length of gap. Now we can independently change the gap and stroke allowing for an option to animate as well.

For multiple stacked rings each successive ring must start from the point where the previous ring stopped. This is achieved by stroke-dashoffset. It allows us to offset the dash's start position along the path. The offset must be the sum total of the previous stroke lengths of all the previous rings.

Let us start with 3 hardcoded rings. Each ring is identical and is stacked on top of eachother.

<svg id="chart" viewBox="0 0 48 48">
    <path id="c1" d="M24,4 a20,20 0 0,1 0,40 a20,20 0 0,1 0,-40 z" fill="none" stroke="#144E7F" stroke-width="8" />
    <path id="c2" d="M24,4 a20,20 0 0,1 0,40 a20,20 0 0,1 0,-40 z" fill="none" stroke="#7BB1DF" stroke-width="8" />
    <path id="c3" d="M24,4 a20,20 0 0,1 0,40 a20,20 0 0,1 0,-40 z" fill="none" stroke="#CECBE6" stroke-width="8" />
</svg>
#chart > path {
    transition: all 500ms ease;
    stroke-dasharray: 0 1; /* To keep it hidden initially */
}

Now, putting it all together. We'll get something like this.

var length;
window.onload = function () {
    length = document.getElementById("c1").getTotalLength();
    // initialising all the rings to 0 length
    document.getElementById("c1").style.strokeDasharray = `0 ${length}`;
    document.getElementById("c2").style.strokeDasharray = `0 ${length}`;
    document.getElementById("c3").style.strokeDasharray = `0 ${length}`;
}

function showChart(data) {
    var total = data[0] + data[1] + data[2];
    // amount of stroke visible
    var stroke = {
        c3: length * (data[0] / total),
        c2: length * (data[1] / total),
        c1: length * (data[2] / total)
    }
    document.getElementById("c1").style.strokeDashoffset = `${-stroke.c2 - stroke.c3}`;
    document.getElementById("c1").style.strokeDasharray = `${stroke.c1} ${length - stroke.c3}`;
    document.getElementById("c2").style.strokeDashoffset = `${-stroke.c3}`;
    document.getElementById("c2").style.strokeDasharray = `${stroke.c2} ${length - stroke.c2}`;
    document.getElementById("c3").style.strokeDasharray = `${stroke.c3} ${length - stroke.c3}`;
}

setTimeout(function(){ showChart([12,18,25]); }, 1200);

This is an easy way to create an animated donut chart. However, it is not without flaws. The chart maybe crisp but since the rings are overlapping, the edges may not align perfectly. The no. of data is also limited but this problem is trivial.

Resources

  1. Stroke Dasharray MDN
  2. Stroke Dashoffset MDN
Executing a smooth animation end
Comprehensive overview of equations in Word