Making an animated donut chart with d3.js
By: Kj Schmidt
Tags: Data Visualization, JavaScript, Data, Donut Chart, D3js

Making a donut chart with d3.js is not as difficult as it may seem. We’ll start by making a simple donut chart, then add buttons to switch between data sets with a smooth, animated transition. See an example of my donut chart in action here.

First things first, this tutorial uses d3.js version 4.6.0. I’m using the CDN found on Cloudflare.

<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.6.0/d3.min.js"></script>

I put this script tag at the end of my html before the script tags for my graphs.

Add a div with the ID #donut to your html where you’d like the donut chart to go.

<div id="donut"></div>

After adding those to the html file, create a new javascript file, where your code for your donut chart will be (don’t forget to link to your new js file in the html, after the d3.js CDN).

Getting started with the javascript

Let’s put some data in, even if it’s just dummy data, so you have something to work with. The data I’m using for this donut chart is about ice cream flavor preferences and is provided by YouGov. I’m putting the data directly into my javascript file.

My data:

//both female and male data

var totals = [{

title: "Soft-serve",

value: 286,

all: 1098

},

{

title: "Scooped",

value: 472,

all: 1098

},

{

title: "No Preference",

value: 318,

all: 1098

},

{

title: "Not Sure",

value: 22,

all: 1098

}

]; //female

var femaleData = [{

title: "Soft-serve",

value: 25,

all: 100

},

{

title: "Scooped",

value: 44,

all: 100

},

{

title: "No Preference",

value: 28,

all: 100

},

{

title: "Not Sure",

value: 3,

all: 100

}

]; //male

var maleData = [{

title: "Soft-serve",

value: 27,

all: 100

},

{

title: "Scooped",

value: 42,

all: 100

},

{

title: "No Preference",

value: 30,

all: 100

},

{

title: "Not Sure",

value: 2,

all: 100

}

];

Next, let’s set up some variables we’ll use for sizing our donut chart. Feel free to adjust these to your needs.

var width = 360;

var height = 360;

var radius = Math.min(width, height) / 2;

var donutWidth = 75; //This is the size of the hole in the middle

We also need to set up a color variable. If you aren’t sure what colors you’d like, you can use a d3 scheme. You can also customize your colors. I chose really bright colors for my chart, because ice cream = summer and summer = bright colors. Replace my colors with ones of your choosing.

//Only choose one! This one for a d3 color scheme:

var color = d3.scaleOrdinal(d3.schemeCategory20c); //Or this one for a customized color scheme:

var color = d3.scaleOrdinal()

.range(["#5A39AC", "#DD98D6", "#E7C820", "#08B2B2"]);

Onto making the chart! If you’ve made a pie chart with d3 before, this is will look very familiar. We are basically doing the same thing here- creating a pie chart, but making use of innerRadius.

var svg = d3.select('#donut')

.append('svg')

.attr('width', width)

.attr('height', height)

.append('g')

.attr('transform', 'translate(' + (width / 2) + ',' + (height / 2) + ')'); var arc = d3.arc()

.innerRadius(radius - donutWidth)

.outerRadius(radius); var pie = d3.pie()

.value(function (d) {

return d.value;

})

.sort(null); var path = svg.selectAll('path')

.data(pie(totals))

.enter()

.append('path')

.attr('d', arc)

.attr('fill', function (d, i) {

return color(d.data.title);

})

.attr('transform', 'translate(0, 0)')

Optional: adding a legend. If you don’t like this style or placement, there are many other options. I wanted the legend to be in the middle of the donut, and to have circular keys.

var legendRectSize = 13;

var legendSpacing = 7; var legend = svg.selectAll('.legend') //the legend and placement

.data(color.domain())

.enter()

.append('g')

.attr('class', 'circle-legend')

.attr('transform', function (d, i) {

var height = legendRectSize + legendSpacing;

var offset = height * color.domain().length / 2;

var horz = -2 * legendRectSize - 13;

var vert = i * height - offset;

return 'translate(' + horz + ',' + vert + ')';

}); legend.append('circle') //keys

.style('fill', color)

.style('stroke', color)

.attr('cx', 0)

.attr('cy', 0)

.attr('r', '.5rem'); legend.append('text') //labels

.attr('x', legendRectSize + legendSpacing)

.attr('y', legendRectSize - legendSpacing)

.text(function (d) {

return d;

});

Transitioning between data sets

Let’s add buttons to the html first. When these buttons are clicked, the data will move between data sets. I put the buttons below the #donut div, not inside it. Add ID’s here that you can reference later in the javascript.

<div>

<button id="everyone">Everyone surveyed</button>

<button id="women">Only women surveyed</button>

<button id="men">Only men surveyed</button>

</div>

Back to the javascript. When these buttons are clicked, we’re going to call a function that changes the dataset (which I’ve named change() ) and redraws the graph. Before we get to the change function, let’s write the onclick functions:

d3.select("button#everyone")

.on("click", function () {

change(totals);

})

d3.select("button#women")

.on("click", function () {

change(femaleData);

})

d3.select("button#men")

.on("click", function () {

change(maleData)

})

The change function redraws the graph, so many parts of this function look familiar. We’re just feeding in new data.

function change(data) {

var pie = d3.pie()

.value(function (d) {

return d.value;

}).sort(null)(data); var width = 360;

var height = 360;

var radius = Math.min(width, height) / 2;

var donutWidth = 75; path = d3.select("#donut")

.selectAll("path")

.data(pie); // Compute the new angles

var arc = d3.arc()

.innerRadius(radius - donutWidth)

.outerRadius(radius); path.transition().duration(500).attr("d", arc); // redrawing the path with a smooth transition

}

The key part of this function is the last line. That’s where the transition happens! You can play around with it to get your desired effect.

Another key part is the .sort(null)(data) . If you don’t include the .sort(null) , it redraws the graphs from lowest to highest segments. I found it to be confusing if my segments were not consistently in the same order. But I did find watching them reorder more fun to watch. ¯\_(ツ)_/¯

Note: if you’d like to experiment with this, only take out the .sort(null) . Leave (data) where it is.

You did it! You made a donut chart that transitions between data sets!

If you have any issues and want to see my full code, check it out on github.