d3.js donut rounded corners + the mystery of PI

Bryony Miles
3 min readNov 23, 2016

--

The client wants rounded corners on the end of a d3.js donut — simple I say, nothing could be further from the truth.

This is not part of d3.js functionality, what you have to do is add a circle of the same colour at the end.

With a single d3.donut this is can be done relatively smoothly: append a circle to same svg and use arc.centroid.

In my case, I had multiple donuts, with d3 generating multiple svgs so every time I tried to solve this I ended up with multiple circles….

With me so far? This led me to revisit the world of circles, PI, radians and trig which I thought was worth noting for posterity.

So here’s the pretty diagram:

The circumference of a circle with a radius of 1 is 2 x Pi.

My aim: To find the co-ordinates x and y for a given %. I’d already set up a d3 scale to return the the pie co-ordinate.

var pie_scale = d3.scaleLinear().domain([0, 100]).range([0, 2 * Math.PI]);

This returns a value >0 and <= 2 Pi (i.e the red dot point along the circumference line from >0 to 2 Pi)

This value is the angle (pink in the diagram) so to calculate x and y I’m advised I need to use the formulas:

var x = (radius * Math.cos(angle)) + ((2*radius)/2)
var y = (radius * Math.sin(angle)) + ((2*radius)/2)

Job done? No, the points were on the circle line but at the wrong place…

I then wasted a lot of time looking at the seperate corners on the circle, dividing the circle into quarters and building formulas.

Then, I realised two things.

  • The formula was working on an anti-clockwise Pi circumference
  • The formula was based on a different 0 point.

This correction works a treat.

if (angle < Math.PI/2){ angle = (2*Math.PI)- angle } else{angle = angle — (Math.PI/2) }

So if the angle is less than Pi/2 return 2*Pi — angle.
Otherwise, return angle — Pi/2.

But then for the final twist. I didn’t want my circles on the outside but in the between my innerRadius and myOuterRadius. So the final working formula is:

if (angle < Math.PI/2){ angle = (2*Math.PI)- angle } else{angle = angle — (Math.PI/2) }

var x = (outerRadius-innerRadius * Math.cos(angle)) + ((2*radius)/2)
var y = (outerRadius-innerRadius * Math.sin(angle)) + ((2*radius)/2)

And this is what it looks like:

--

--