d3.js饼图传播标签d3.js饼图传播标签(d3.js spreading labels for

2019-05-12 07:42发布

我使用d3.js - 我有一个饼图在这里。 这个问题虽然是当片为小 - 标签重叠。 什么是散开标签的最佳途径。

http://jsfiddle.net/BxLHd/16/

下面是标签的代码。 我很好奇 - 这可能嘲笑与D3三维饼图?

                        //draw labels                       
                        valueLabels = label_group.selectAll("text.value").data(filteredData)
                        valueLabels.enter().append("svg:text")
                                .attr("class", "value")
                                .attr("transform", function(d) {
                                    return "translate(" + Math.cos(((d.startAngle+d.endAngle - Math.PI)/2)) * (that.r + that.textOffset) + "," + Math.sin((d.startAngle+d.endAngle - Math.PI)/2) * (that.r + that.textOffset) + ")";
                                })
                                .attr("dy", function(d){
                                        if ((d.startAngle+d.endAngle)/2 > Math.PI/2 && (d.startAngle+d.endAngle)/2 < Math.PI*1.5 ) {
                                                return 5;
                                        } else {
                                                return -7;
                                        }
                                })
                                .attr("text-anchor", function(d){
                                        if ( (d.startAngle+d.endAngle)/2 < Math.PI ){
                                                return "beginning";
                                        } else {
                                                return "end";
                                        }
                                }).text(function(d){
                                        //if value is greater than threshold show percentage
                                        if(d.value > threshold){
                                            var percentage = (d.value/that.totalOctets)*100;
                                            return percentage.toFixed(2)+"%";
                                        }
                                });

                        valueLabels.transition().duration(this.tweenDuration).attrTween("transform", this.textTween);
                        valueLabels.exit().remove();

Answer 1:

作为@The古县发现,以前的答案我张贴在Firefox中失败,因为它依赖于SVG方法.getIntersectionList()找到冲突,并且该方法还没有在Firefox尚未实现 。

这只是意味着我们必须跟踪标签位置和试验冲突自己的。 随着D3,最有效的方法来检查布局冲突使用包括四叉树数据结构来存储位置,这样你就不必检查每个标签重叠,只是那些在可视化的类似区域。

从以前的答案代码的第二部分被替换为:

        /* check whether the default position 
           overlaps any other labels*/
        var conflicts = [];
        labelLayout.visit(function(node, x1, y1, x2, y2){
            //recurse down the tree, adding any overlapping labels
            //to the conflicts array

            //node is the node in the quadtree, 
            //node.point is the value that we added to the tree
            //x1,y1,x2,y2 are the bounds of the rectangle that
            //this node covers

            if (  (x1 > d.r + maxLabelWidth/2) 
                    //left edge of node is to the right of right edge of label
                ||(x2 < d.l - maxLabelWidth/2) 
                    //right edge of node is to the left of left edge of label
                ||(y1 > d.b + maxLabelHeight/2)
                    //top (minY) edge of node is greater than the bottom of label
                ||(y2 < d.t - maxLabelHeight/2 ) )
                    //bottom (maxY) edge of node is less than the top of label

                  return true; //don't bother visiting children or checking this node

            var p = node.point;
            var v = false, h = false;
            if ( p ) { //p is defined, i.e., there is a value stored in this node
                h =  ( ((p.l > d.l) && (p.l <= d.r))
                   || ((p.r > d.l) && (p.r <= d.r)) 
                   || ((p.l < d.l)&&(p.r >=d.r) ) ); //horizontal conflict

                v =  ( ((p.t > d.t) && (p.t <= d.b))
                   || ((p.b > d.t) && (p.b <= d.b))  
                   || ((p.t < d.t)&&(p.b >=d.b) ) ); //vertical conflict

                if (h&&v)
                    conflicts.push(p); //add to conflict list
            }

        });

        if (conflicts.length) {
            console.log(d, " conflicts with ", conflicts);  
            var rightEdge = d3.max(conflicts, function(d2) {
                return d2.r;
            });

            d.l = rightEdge;
            d.x = d.l + bbox.width / 2 + 5;
            d.r = d.l + bbox.width + 10;
        }
        else console.log("no conflicts for ", d);

        /* add this label to the quadtree, so it will show up as a conflict
           for future labels.  */
        labelLayout.add( d );
        var maxLabelWidth = Math.max(maxLabelWidth, bbox.width+10);
        var maxLabelHeight = Math.max(maxLabelHeight, bbox.height+10);

请注意,我已经改变了参数名称的标签,以L / R / B / T(左/右/底/顶)的边缘,把一切都在我的脑海里逻辑。

住在这里小提琴: http://jsfiddle.net/Qh9X5/1249/

做这种方式的另外一个好处是,你可以检查基于标签的最终位置冲突,其实之前设定的位置。 这意味着您可以使用转换为标签移动到位置搞清楚了所有标签的位置后。



Answer 2:

应该可以做到。 你到底想怎么办就取决于你想与间隔出来的标签做什么。 没有,但是,一个建在这样做的方式。

有标签的主要问题是,在你的榜样,他们依靠定位相同的数据,你正在使用您的饼图的切片。 如果你想他们的空间更多像Excel没有(即给他们的房间),你必须发挥创意。 您掌握的信息是他们的首发位置,他们的身高,和它们的宽度。

一个非常有趣的(我的乐趣的定义)的方式去解决,这将是创建一个随机的求解器标签的最佳安排。 你可以用基于能量的方法做到这一点。 定义,其中基于两个标准能量增加的能量函数:从开始点的距离,并与邻近的标签重叠。 你可以以此为基础进行能量的标准找到与问候你的总能量,这将导致您的标签是尽可能接近它们的原始点没有重叠的显著量的一个局部最优解简单的梯度下降,并没有迫使更多从原来的分分离开。

多少重叠是可以容忍的,将取决于您所指定的能量函数,这应该是可调的给分的一个好看的分布。 同样,有多少你愿意让步的点接近,将取决于你的能量增加功能件的形状从原来的点的距离。 (A线性能量增加将导致更接近点,但更大的异常值。的二次或三次将有更大的平均距离,但较小的异常值。)

也可能存在求解极小的分析方法,但是这将是更难。 你也许可以开发定位的东西启发式,这可能是Excel中做了什么,但是这将是那么好玩了。



Answer 3:

到检查冲突的一种方法是使用<svg>元素的getIntersectionList()方法。 该方法要求在通过SVGRect对象(它是从一个不同的<rect>元素!),例如通过一个图形元件的所返回的对象.getBBox()方法。

有了这两种方法,你可以找出其中的标签是在屏幕内,如果任何重叠。 然而,复杂的是,传递给矩形坐标getIntersectionList的根SVG的坐标内interpretted,而返回的坐标getBBox是局部坐标系统相同。 所以,你还需要方法getCTM()获得累积变换矩阵),以两者之间转换。

我开始从拉斯Khottof的例子是@TheOldCounty在评论贴过,因为它已经包含弧段和标签之间的线路。 我做了一点重新组织放于单独的标签,线和弧段<g>元素。 这避免了奇怪的重叠上更新(对指针线上面描绘的圆弧),也可以很容易地确定哪些元素,我们很担心重叠-唯一的其他标签,而非指针直线或圆弧-通过将母<g>元素作为第二个参数,以getIntersectionList

标签被定位使用一次一个each功能,以及他们将要实际定位(即,设定为它的最终值的属性,没有过渡)当时的位置被计算,以便它们在适当位置时getIntersectionList被称为下一个标签的默认位置。

如果它重叠以前的标签,其中移动标签的决定是一个复杂的,如@ ckersch的答案概括。 我把它简单,只是将其移动到所有重叠元素的权利。 这可能导致在饼图,其中从上段标签可以移动,使他们从第一部分重叠标签顶部的一个问题,但如果饼图由段大小排序那是不可能的。

这里的关键代码:

    labels.text(function (d) {
        // Set the text *first*, so we can query the size
        // of the label with .getBBox()
        return d.value;
    })
    .each(function (d, i) {
        // Move all calculations into the each function.
        // Position values are stored in the data object 
        // so can be accessed later when drawing the line

        /* calculate the position of the center marker */
        var a = (d.startAngle + d.endAngle) / 2 ;

        //trig functions adjusted to use the angle relative
        //to the "12 o'clock" vector:
        d.cx = Math.sin(a) * (that.radius - 75);
        d.cy = -Math.cos(a) * (that.radius - 75);

        /* calculate the default position for the label,
           so that the middle of the label is centered in the arc*/
        var bbox = this.getBBox();
        //bbox.width and bbox.height will 
        //describe the size of the label text
        var labelRadius = that.radius - 20;
        d.x =  Math.sin(a) * (labelRadius);
        d.sx = d.x - bbox.width / 2 - 2;
        d.ox = d.x + bbox.width / 2 + 2;
        d.y = -Math.cos(a) * (that.radius - 20);
        d.sy = d.oy = d.y + 5;

        /* check whether the default position 
           overlaps any other labels*/

        //adjust the bbox according to the default position
        //AND the transform in effect
        var matrix = this.getCTM();
        bbox.x = d.x + matrix.e;
        bbox.y = d.y + matrix.f;

        var conflicts = this.ownerSVGElement
                            .getIntersectionList(bbox, this.parentNode);

        /* clear conflicts */
        if (conflicts.length) {
            console.log("Conflict for ", d.data, conflicts);   
            var maxX = d3.max(conflicts, function(node) {
                var bb = node.getBBox();
                return bb.x + bb.width;
            })

            d.x = maxX + 13;
            d.sx = d.x - bbox.width / 2 - 2;
            d.ox = d.x + bbox.width / 2 + 2;

        }

        /* position this label, so it will show up as a conflict
           for future labels. (Unfortunately, you can't use transitions.) */
        d3.select(this)
            .attr("x", function (d) {
                return d.x;
            })
            .attr("y", function (d) {
                return d.y;
            });
    });

而这里的工作小提琴: http://jsfiddle.net/Qh9X5/1237/



文章来源: d3.js spreading labels for pie charts