I will put a text element in every arc of my Pie Chart (center) - as shown in this example: http://bl.ocks.org/mbostock/3887235
But I will only put the text element if the room is sufficient for the whole text, so im must compare the size of my text element with the "available" space in every arc.
I think I can do this with getBBox() to get the text dimensions... but how can I get (and compare) the dimension of the available space in every arc.
thx...!
Add Labels to the Chart To add labels, right-click on any slice in the pie, then click Add Data Labels, in the popup menu.
In a pie chart, the arc length of each slice (and consequently its central angle and area) is proportional to the quantity it represents. While it is named for its resemblance to a pie which has been sliced, there are variations on the way it can be presented.
This question has been asked several times before.
The solutions I have suggested there is to rotate
the label but it has never quite satisfied me. Part of it was the horrible font rendering done by some browsers and loss in legibility that brings and the weird flip
when one label crosses over the 180°
line. In some cases, the results were acceptable and unavoidable, e.g. when the labels were too long.
One of the other solution, the one suggested by Lars, is to put the labels outside the pie chart. However, that just pushes the labels outside, granting them a larger radius, but does not solve the overlap
problem completely.
The other solution is actually using the technique you suggest: just remove the labels which do not fit.
Compare Original, which has >= 65
label overflowing to Solution where the overflowing label is gone.
The key insight is to see that this problem is of finding whether one convex polygon (a rectangle, the bounding box) is contained inside another convex polygon(-ish) (a wedge).
The problem can be reduced to finding whether all the points of the rectangle lie inside the wedge or not. If they do, then the rectangle lies inside the arc.
Now that part is easy. All one needs to do is to check:
radius
startAngle
and endAngle
of the arc.function pointIsInArc(pt, ptData, d3Arc) {
// Center of the arc is assumed to be 0,0
// (pt.x, pt.y) are assumed to be relative to the center
var r1 = d3Arc.innerRadius()(ptData), // Note: Using the innerRadius
r2 = d3Arc.outerRadius()(ptData),
theta1 = d3Arc.startAngle()(ptData),
theta2 = d3Arc.endAngle()(ptData);
var dist = pt.x * pt.x + pt.y * pt.y,
angle = Math.atan2(pt.x, -pt.y); // Note: different coordinate system.
angle = (angle < 0) ? (angle + Math.PI * 2) : angle;
return (r1 * r1 <= dist) && (dist <= r2 * r2) &&
(theta1 <= angle) && (angle <= theta2);
}
Now that we have that out of the way, the second part is figuring out what are the four corners of the rectangle. That, also, is easy:
g.append("text")
.attr("transform", function(d) { return "translate(" + arc.centroid(d) + ")"; })
.attr("dy", ".35em")
.style("text-anchor", "middle")
.text(function(d) { return d.data.age; })
.each(function (d) {
var bb = this.getBBox(),
center = arc.centroid(d);
var topLeft = {
x : center[0] + bb.x,
y : center[1] + bb.y
};
var topRight = {
x : topLeft.x + bb.width,
y : topLeft.y
};
var bottomLeft = {
x : topLeft.x,
y : topLeft.y + bb.height
};
var bottomRight = {
x : topLeft.x + bb.width,
y : topLeft.y + bb.height
};
d.visible = pointIsInArc(topLeft, d, arc) &&
pointIsInArc(topRight, d, arc) &&
pointIsInArc(bottomLeft, d, arc) &&
pointIsInArc(bottomRight, d, arc);
})
.style('display', function (d) { return d.visible ? null : "none"; });
The pith of the solution is in the each
function. We first place the text at the right place so that the DOM renders it. Then we use the getBBox()
method to get the bounding box of the text
in the user space. A new user space is created by any element which has a transform
attribute set on it. That element, in our case, is the text
box itself. So the bounding box returned is relative to the center of the text, as we have set the text-anchor
to be middle
.
The position of the text
relative to the arc
can be calculated since we have applied the transformation 'translate(' + arc.centroid(d) + ')'
to it. Once we have the center, we just calculate the topLeft
, topRight
, bottomLeft
and bottomRight
points from it and see whether they all lie inside the wedge
.
Finally, we determine if all the points lie inside the wedge and if they do not fit, set the display
CSS property to none
.
Original
Solution
I am using the innerRadius
which, if non zero, makes the wedge
non-convex which will make the calculations much more complex! However, I think the danger here is not significant since the only case it might fail is this, and, frankly, I don't think it'll happen often (I had trouble finding this counter example):
x
and y
are flipped and y
has a negative sign while calculating Math.atan2
. This is because of the difference between how Math.atan2
and d3.svg.arc
view the coordinate system and the direction of positive y
with svg
.
Coordinate system for Math.atan2
θ = Math.atan2(y, x) = Math.atan2(-svg.y, x)
Coordinate system for d3.svg.arc
θ = Math.atan2(x, y) = Math.atan2(x, -svg.y)
You can't really do this with the bounding box because the bounding box is much larger than a wedge for the pie chart wedges. That is, even though the wedge at the outer edge would be wide enough to accommodate the text, that doesn't mean that it's wide enough at the actual position of the text.
Unfortunately, there's no easy way of doing what you're trying to do (pixel-level overlap testing). See e.g. this question for some more information. I would suggest simply putting the text labels outside of the pie chart so you don't run into this problem.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With