Specifically for pie charts, the d3.layout.pie()
function will format data with a startAngle
and endAngle
attributes. The radius can be whatever you desire (how far out from the center you would like to place the label).
Combining these pieces of information with a couple trigonometric functions lets you determine the x and y coordinates for labels.
Consider this gist/block.
Regarding the x/y positioning of the text, the magic is in this line (formatted for readability):
.attr("transform", function(d) {
return "translate(" +
( (radius - 12) * Math.sin( ((d.endAngle - d.startAngle) / 2) + d.startAngle ) ) +
", " +
( -1 * (radius - 12) * Math.cos( ((d.endAngle - d.startAngle) / 2) + d.startAngle ) ) +
")";
})
((d.endAngle - d.startAngle) / 2) + d.startAngle
gives us our angle (theta) in radians.
(radius - 12)
is the arbitrary radius I chose for the position of the text.
-1 *
the y axis is inverted (see below).
The trig functions used are: cos = adjacent / hypotenuse
and sin = opposite / hypotenuse
. But there are a couple things we need to consider to make these work with our labels.
- 0 angle is at 12 o'clock.
- The angle still increases in a clockwise direction.
- The y axis is inverted from the standard cartesian coordinate system. Positive y is in the direction of 6 o'clock - down.
- Positive x is still in the direction of 3 o'clock - right.
That messes things up quite a bit and basically has the effect of swapping sin
and cos
. Our trig functions then become: sin = adjacent / hypotenuse
and cos = opposite / hypotenuse
.
Substituting variable names we have sin(radians) = x / r
and cos(radians) = y / r
. After some algebraic manipulation we can get both functions in terms of x and y respectively r * sin(radians) = x
and r * cos(radians) = y
. From there, just plug those into the transform/translate attribute.
That'll put the labels in the right location, to make them look fancy, you need some styling logic like this:
.style("text-anchor", function(d) {
var rads = ((d.endAngle - d.startAngle) / 2) + d.startAngle;
if ( (rads > 7 * Math.PI / 4 && rads < Math.PI / 4) || (rads > 3 * Math.PI / 4 && rads < 5 * Math.PI / 4) ) {
return "middle";
} else if (rads >= Math.PI / 4 && rads <= 3 * Math.PI / 4) {
return "start";
} else if (rads >= 5 * Math.PI / 4 && rads <= 7 * Math.PI / 4) {
return "end";
} else {
return "middle";
}
})
This will make the labels from 10:30 o'clock to 1:30 o'clock and from 4:30 o'clock to 7:30 o'clock anchor in the middle (they are above and below), the labels from 1:30 o'clock to 4:30 o'clock anchor on the left (they are to the right), and the labels from 7:30 o'clock to 10:30 o'clock anchor on the right (they are to the left).
The same formulas can be used for any D3 radial graph, the only difference is how you determine the angle.
I hope this helps anyone stumbling across it!