Inpidual donut segments

In the previous section, you've seen that we call the updateCircle(show) function when the data is loaded, or when we selected something else in our dropdown. In this part, we'll look how, based on the selected item, we update or add new donut segments, and animate the changes. Let's start by collecting the data we want to show:

function updateCircle(toShow) { 
// get the data for all the Firms
var filtered = loadedData.filter(filterData(toShow));
var totalFirms = filtered.reduce(function (total, el) {return total + +el.count}, 0);

// define the inpidual arc segments
var pie = d3.pie()
.sort(null)
.padAngle(0.04)
.value(function (d) {
return +d.count;
});

// create the arcs segments, the data property of the arcs[i] represents data.
var arcs = pie(filtered);
...
}

In this section, we pass the data we loaded from the CSV file through a filter (loadedData.filter(filterData(toShow))), which filters out the data specified by the toShow argument. We won't show the implementation of the filterData function, since it is just plain JavaScript. If you're interested, look at the source code: <DVD3/src/chapter-02/D02-01.js>. We also calculate the total number of firms for this group that needs to be shown, which we'll use later on to calculate the percentage that we show in the center of the donut when you hover the mouse over a segment of the donut. The interesting part in the code is the d3.pie() function. Previously, we saw the d3.arc() function, which allows us to create a single donut segment. The d3.pie() function allows us to generate startAngle, endAngle, and paddingAngle based on the provided data, which we can then pass to a d3.arc() function. The d3.pie() function also can sort the data by passing in a function to sort (we use null for no sorting).
The d3.pie() function needs a numeric value to determine the angles. Since our data is in an object, we use the value function to return the count property of our data. This will help the d3.pie() function return the correct angles. After defining the pie, we use it by passing in our filtered data. The result from this (the arcs variable), is just a JavaScript array containing angles:

The cool thing about the d3.pie() function is that we can now bind the arcs variable and use that to create our inpidual donut segments. Before we start animating them, we'll first just add them normally:

var arcElements = pieContainer.selectAll(".arc").data(arcs); 

// handle the elements
arcElements.enter()
.append("path")
.attr("class", "arc")
.style("fill", function (d, i) { return colors(i) })
.merge(arcElements)
.attr("d", arc)

Nothing new here. We just select all the elements with the class .arc and bind the configuration (arcs) generated by d3.pie() function using the data() function. To render a donut segment, we create a path element, set the fill based on the color interpolator we saw earlier, and generate the d attribute (which represents the SVG path), by calling the arc function (this is called with each of the elements from the generated arcs array). The result is a set of donut segments:

Now, let's see what we need to change to animate the donut segments instead of just specifying the d attribute directly. First, we'll show the code, and then explain the various parts:

// remove .attr("d", arc) and replace with 
arcElements.enter()
.append("path")
.attr("class", "arc")
.style("fill", function (d, i) { return colors(i) })
.merge(arcElements)
.transition()
.ease(d3.easeCircle)
.duration(2000)
.attrTween("d", tweenArcs);

function tweenArcs(d) {
var interpolator = getArcInterpolator(this, d);
return function (t) {
return arc(interpolator(t));
};
}

function getArcInterpolator(el, d) {
var oldValue = el._oldValue;
var interpolator = d3.interpolate({
startAngle: oldValue ? oldValue.startAngle : 0,
endAngle: oldValue ? oldValue.endAngle : 0
}, d);
el._oldValue = interpolator(0);
return interpolator;
}

In this piece of code, you can see that we replaced the attr("d", arc) function call with a transition call. A transition manages the animation from one state to another. So, in our case, it allows us to transition the size of a donut segment. Transition in itself is a very big subject, so we'll show it in more detail in later chapters. The following table shows the most important transition-related API calls:

When you create a transition, you can also specify the ease function. An easing function defines how a value transitions from the start to the end value. If you specify d3.linear, the value will follow a linear path; with d3.easeElasticInOut, it will bounce at the beginning and the end of a transition; and D3 offers a large set of other transitions. A very easy way to see what is possible is by using the easing explorer (http://bl.ocks.org/mbostock/248bac3b8e354a9103c4):

So, back to the code. Let's look a bit closer at the code that defines the transition for our donut segments:

arcElements.enter() 
.append("path")
.attr("class", "arc")
.style("fill", function (d, i) { return colors(i) })
.merge(arcElements)
.transition()
.ease(d3.easeCircle)
.duration(2000)
.attrTween("d", tweenArcs);

Looking at the previous table and this code, we can see what is happening. We're creating a new transition that is executed whenever the arcElements are created or updated. In this transition, we use the d3.easeCircle easing function. It will take two seconds, and change the d attribute using a custom interpolator function (tweenArcs). The reason why we need a custom interpolator is because we need to calculate a new d value (the SVG path that draws our donut segment) based on the old value (remember, this was generated from the pie() function), and the new value. If we just wanted to change a simple value, we could have used the attr function. For our example, what we need to return is an interpolator that can interpolate the startAngle and the endAngle and, based on these values, return a new SVG path that we can assign to the d attribute:

function tweenArcs(d) { 
var interpolator = getArcInterpolator(this, d);
return function (t) {
return arc(interpolator(t));
};
}

function getArcInterpolator(el, d) {
var oldValue = el._oldValue;
var interpolator = d3.interpolate({
startAngle: oldValue ? oldValue.startAngle : 0,
endAngle: oldValue ? oldValue.endAngle : 0
}, d);
el._oldValue = interpolator(0);
return interpolator;
}

As you can see, our tweenArcs function takes the new data value (d) and uses that to create an interpolator with the getArcInterpolator function. This function uses the d3.interpolate function to create an interpolator that interpolates the startAngle and the endAngle. Note that we explicitly keep track of the previous value by storing that in the _oldValue property on the element itself (this). We don't directly return the interpolator created by the getArcInterpolator function, but convert the result from that interpolator to a SVG path by calling the arc(interpolator(t)) function. With this setup, we've got an interpolator that returns the correct SVG paths for our d attribute. If we add this and open the page, you can see that arcs grow to the correct sizes automatically:

Now that we've got the basic arcs rendered, we can add some additional information that explains what this donut represents. In the next two sections, we'll use the same principles to add and animate text labels, and the lines pointing to these labels. First, we add the text labels.