D3如果有足够的空间,将圆弧标签放在饼图中 [英] D3 put arc labels in a Pie Chart if there is enough space

查看:164
本文介绍了D3如果有足够的空间,将圆弧标签放在饼图中的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我将在我的饼图(中心)的每个弧中放置一个文本元素,如下例所示:
。这是因为 Math.atan2 d3.svg.arc 之间如何区别查看坐标系和方向的 y svg



Math.atan2 的协调系统

θ= Math.atan2(y,x)= Math.atan2 svg.y,x)



d3.svg.arc的协调系统

code> d3.svg.arc< / code>> θ= Math.atan2(x,y)= Math.atan2(x,-svg.y) p>


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...!

解决方案

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.

Hide overflowing labels

Compare Original, which has >= 65 label overflowing to Solution where the overflowing label is gone.

Reducing the problem

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.

Does a point lie inside a wedge

Now that part is easy. All one needs to do is to check:

  1. The distance of the point from the center is less than the radius
  2. The angle subtended by the point on the center is between the 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);
}

Find the bounding box of the labels

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.

Working demo

Original

Solution

Note

  1. 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):

  2. 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)

这篇关于D3如果有足够的空间,将圆弧标签放在饼图中的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆