I\'m new to d3.js and I\"m trying to make a Pie-chart with it. I have only one problem: I can\'t get my labels outside my arcs... The labels are positioned with arc.centroid
Thanks!
I found a different way to solve this problem, but yours seems better :-)
I created a second arc with a bigger radius and used it to position my labels.
///// Arc Labels /////
// Calculate position
var pos = d3.svg.arc().innerRadius(r + 20).outerRadius(r + 20);
// Place Labels
arcs.append("svg:text")
.attr("transform", function(d) { return "translate(" +
pos.centroid(d) + ")"; })
.attr("dy", 5)
.attr("text-anchor", "middle")
.attr("fill", function(d, i) { return colorL(i); }) //Colorarray Labels
.attr("display", function(d) { return d.value >= 2 ? null : "none"; })
.text(function(d, i) { return d.value.toFixed(0) + "%"});
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.
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!