Donut chart

Dataviz logo representing a Doughnut chart.

The donut chart is a very common yet criticized way to represent the value of a few groups in a dataset. It is very close to the pie chart and thus suffers the same downsides.

This page explains how to build a donut chart using d3.js and React. It starts with a basic example and then focus on customization like legends, hover effect and dataset transition.

Useful links

The Data

The dataset required to build a donut chart is an array where each item represents a group. Each item is an object with 2 properties. They provide the group name (name) and its value (value).


For instance, here is the dataset used for the simple donut chart below:

const data = [
  {name:"Mark", value: 90},
  {name:"Robert", value: 12},
  {name:"Emily", value: 34},
  {name:"Marion", value: 53},
  {name:"Nicolas", value: 98},
]

Most basic donut chart

The process to build a donut chart is highly similar as the process to build a pie chart. It is extensively described in its dedicated section.

Long size tutorial

Basically, the pie() function of d3 is used to compute the start and end angles of each group.

const pieGenerator = d3.pie().value((d) => d.value);
const pie = pieGenerator(data);

This allows to compute arcs thanks to the arc() function of d3. This function expects a innerRadius argument that controls the size of the inner circle of the donut chart. The only difference between a pie and a donut is this inner radius.

const arcPathGenerator = d3.arc();
const arcs pie.map((p) =>
      arcPathGenerator({
        innerRadius: 50, // makes a donut instead of a pie
        outerRadius: radius,
        startAngle: p.startAngle,
        endAngle: p.endAngle,
      })
    );

And that's it. This array of path can be renderer with react using a map as shown in the example below.



Basic donut chart with react and d3.js

Adding inline labels

The minimal donut chart above is completely useless as long as it does not display the group names. Adding a side legend would be straightforward but that's a bad practice. It's indeed very annoying for the reader to continuously switch between the legend and the chart.

Instead I suggest to add clean inline labels. A little dot will be located on each slice centroid. From there a first segment will go out of the donut, followed by a second horizontal segment that goes to the label.

The difficulty here is to get the position of the slice centroid and of the line inflexion point.

The centroid() function of d3 is all we need for that. It gives the x and y positions of the centroid of an arc, arc that we used to build the donut slice anyway.

const sliceInfo = {
  innerRadius,
  outerRadius: radius,
  startAngle: start,
  endAngle: end,
};
const centroid = arcGenerator.centroid(sliceInfo); // [x,y] position of the centroid
const slicePath = arcGenerator(sliceInfo); // string: the path of the slice

For more in depth explanation, please refer to the pie chart section that uses the exact same process.

Mark (90)Robert (12)Emily (34)Marion (53)Nicolas (58)

A donut chart with clean inline legends, built thanks to the centroid function of d3.js.

This approach is a good start when it comes to add legend on a donut chart. It has some limitations though.

If many groups are available, we will likely get some overlaps between labels, resulting in a messy figure. This could be avoided but would require a good amount of additional code. It is thus ignored here.

Responsive Donut with react

The component above is not responsive. It expects 2 props called width and height and will render a Donut of those dimensions.

Making the Donut responsive requires adding a wrapper component that gets the dimension of the parent div, and listening to a potential dimension change. This is possible thanks to a hook called useDimensions that will do the job for us.

useDimensions: a hook to make your viz responsive
export const useDimensions = (targetRef: React.RefObject<HTMLDivElement>) => {

  const getDimensions = () => {
    return {
      width: targetRef.current ? targetRef.current.offsetWidth : 0,
      height: targetRef.current ? targetRef.current.offsetHeight : 0
    };
  };

  const [dimensions, setDimensions] = useState(getDimensions);

  const handleResize = () => {
    setDimensions(getDimensions());
  };

  useEffect(() => {
    window.addEventListener("resize", handleResize);
    return () => window.removeEventListener("resize", handleResize);
  }, []);

  useLayoutEffect(() => {
    handleResize();
  }, []);

  return dimensions;
}

I'm in the process of writing a complete blog post on the topic. Subscribe to the project to know when it's ready.




Donut inspiration

If you're looking for inspiration to create your next Donut, note that dataviz-inspiration.com showcases many examples. Definitely the best place to get ... inspiration!

dataviz-inspiration.com showcases hundreds of stunning dataviz projects. Have a look to get some ideas on how to make your Donut looks good!

visit

Hover effect

On the graph below, hovering over a slice will smoothly highlight it, giving a nice polish touch to the widget.

The process is quickly described on the pie chart hover effect section. But hover effect is a topic on itself. As a result, I plan to write a full tutorial targeting this topic only. You can subscribe to know when it's ready!

Tell me when the Hover effect post is ready!

Meanwhile, here is a donut chart with a hover effect, together with its React code:

Mark (90)Robert (12)Emily (34)Marion (53)Nicolas (58)

A donut chart with clean inline legends, built thanks to the centroid function of d3.js.

Data transition

The Pie component expects a data prop. What should we do when this data changes?

By default, the chart will update instantly, with no transition. Adding a smooth transition gives a nice polish touch to the graph. Try to switch between the 2 datasets below to see the animation in action.

The code below relies on the react-spring library. Instead of rendering a path for each slice, it uses a animated.path component that handles the spring animation for us.

The implementation is not trivial. I plan to publish a full tutorial on react-spring for data visualization soon. You can subscribe here to be notified when it is ready.

A donut chart with clean inline legends, built thanks to the centroid function of d3.js.

Note: check the blue group that appears / disappears between dataset. This kind of enter/exit pattern is something to keep in mind when building animations.

Pie chart to barplot

Pie charts are often criticized since angles are hard to read. Let's represent the same data using a pie chart or a barplot, to see what's the most insightful 🤷‍♂️.

Note that here we animate the transition between different shape types: each arc becomes a rectangle and reciprocally. This is made possible thanks to the flubber library, used in coordination with react-spring.

Once more, a full tutorial is needed here. You can subscribe here to be notified when it is ready. In the meanwhile, the code of this specific example is provided below.

Transition from a pie chart to a barplot with a smooth animation using the buttons on top.

Part Of A Whole

Contact

👋 Hey, I'm Yan and I'm currently working on this project!

Feedback is welcome ❤️. You can fill an issue on Github, drop me a message on Twitter, or even send me an email pasting yan.holtz.data with gmail.com. You can also subscribe to the newsletter to know when I publish more content!