Skip to content
Jonathan George By Jonathan George Software Engineer IV
Donut chart corkscrew entry animation with d3.js

TL;DR I wanted a nice way of animating in a D3 donut chart with a "corkscrew" effect, where segments animate in at constant speed regardless of segment size in a clockwise sweep, simultaneously animating out from the centre. Here's what I came up with:

See the Pen D3.js pie/donut chart corkscrew entry animation by Jonathan George (@jongeorge1) on CodePen.

The Goal

I found some nice examples of pie/donut chart entry animations, where segments animate in one by one, but none of them were quite what I was looking for.

In this example, each segment is animated in turn, meaning that it takes the same amount of time to animate in regardless of its size. This produces a slightly odd effect when a small segment is immediately followed by a large segment.

I also wanted the "corkscrew" effect shown above, where the chart expands from the center point at the same time as it's drawn, and I couldn't find any examples of that.

The Code

The solution to both problems is that each segment has to be animated for the entire duration of the entry animation so that they can be animated out from the center point. We can then add some basic logic to work out when to start and finish drawing each segment, which means segments appear to be drawn at a constant speed regardless of their size.

The first step is to create the path elements in the usual way. A group is added to the SVG to move the chart to the center of the available space, and bound to data created from d3.pie():

let generator = d3.pie()
  .sort(null);

let chart = generator(data);

let arcs = d3.select("#chart")
  .append("g")
  .attr("transform", "translate(100, 100)")
  .selectAll(".arc")
  .data(chart)
  .enter()
  .append("path")
  .style("fill", (d, i) => colors[i]);

With the initial setup done, we need three interpolations:

  1. Between the start and end angles of the pie chart:
let angleInterpolation = d3.interpolate(generator.startAngle()(), generator.endAngle()());

This will allow us to calculate when to start drawing each segment, and how much of the segment to draw.

  1. From the center to the outer radius of our chart:
let outerRadiusInterpolation = d3.interpolate(0, sizes.outerRadius);
The Introduction to Rx.NET 2nd Edition (2024) Book, by Ian Griffiths & Lee Campbell, is now available to download for FREE.

This is used to give the effect of animating out from the center point of the chart.

  1. From the center to the inner radius of our chart:
let innerRadiusInterpolation = d3.interpolate(0, sizes.innerRadius);
Power BI Weekly is a collation of the week's top news and articles from the Power BI ecosystem, all presented to you in one, handy newsletter!

If you're building a pie chart this isn't needed - a pie chart is effectively a donut chart with an innerRadius of 0, and the resulting effect would be the pie chart segments "growing" out from the center as they are drawn.

With that done, we start two transitions. The first is responsible for drawing the arcs.

arcs.transition()
  .duration(durations.entryAnimation)
  .attrTween("d", d => {
    let originalEnd = d.endAngle;
    return t => {
      let currentAngle = angleInterpolation(t);
      if (currentAngle < d.startAngle) {
        return "";
      }

      d.endAngle = Math.min(currentAngle, originalEnd);

      return arc(d);
    };
  });

We use the angle interpolation to determine whether we are at the point where we should have started drawing the current arc or not. If the current angle is prior to the start of the arc, we leave it undrawn. Otherwise we use the current angle to determine how much of the arc to draw using the arc generator.

The second transition is responsible for modifying the arc generator by updating its innerRadius and outerRadius over the duration of the entry animation. As it's effectively a transition for the entire chart, we run it against the root element.

d3.select("#chart")
  .transition()
  .duration(durations.entryAnimation)
  .tween("arcRadii", () => {
    return t => arc
      .innerRadius(innerRadiusInterpolation(t))
      .outerRadius(outerRadiusInterpolation(t));
  });

The effect of this is that as the arcs are redrawn over the course of the first transition they appear to move outward from the center point of the chart.

To see the full code, take a look at the CodePen here.

Jonathan George

Software Engineer IV

Jonathan George

Jon is an experienced project lead and architect who has spent nearly 20 years delivering industry-leading solutions for clients across multiple industries including oil and gas, retail, financial services and healthcare. At endjin, he helps clients take advantage of the huge opportunities presented by cloud technologies to better understand and grow their businesses.