d3.js donut rounded corners + the mystery of PI
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: