13

我正在寻找使用 D3 创建带有浮动标签的饼图。我是 D3 的新手,我什至不确定这是否可行?你能以某种方式在另一个图形中使用一个图形的标签吗?如果可以的话,你能给我举个例子吗?

更简短的说明:我想要来自: http ://bl.ocks.org/1691430 的标签

在此处输入图像描述 ...在饼图上。

这是我在下面运行的代码:或者在 JSBIN 中: http: //jsbin.com/awilak/1/edit

如果我正确理解他的代码,这是添加标签的部分。我不明白 labelForce.update 做了什么。从那里开始,我不关心过渡,所以不需要这条线。然后剩下的只是画圆圈并添加链接/线?如果有人可以整合这将是惊人的,但如果你能帮助我了解发生了什么以及我错过了什么,我将不胜感激。

// Now for the labels
// This is the only function call needed, the rest is just drawing the labels
anchors.call(labelForce.update)

labels = svg.selectAll(".labels")
    .data(data, function(d,i) {return i;})
labels.exit()
    .attr("class","exit")
    .transition()
    .delay(0)
    .duration(500)
    .style("opacity",0)
    .remove();

// Draw the labelbox, caption and the link
newLabels = labels.enter().append("g").attr("class","labels")

newLabelBox = newLabels.append("g").attr("class","labelbox")
newLabelBox.append("circle").attr("r",11)
newLabelBox.append("text").attr("class","labeltext").attr("y",6)
newLabels.append("line").attr("class","link")

labelBox = svg.selectAll(".labels").selectAll(".labelbox")
links = svg.selectAll(".link")
labelBox.selectAll("text").text(function(d) { return d.num})
}

<!DOCTYPE html>
<html>
<head>    
    <meta http-equiv="Content-type" content="text/html; charset=utf-8">
    <title>Testing Pie Chart</title>
    <script type="text/javascript" src="http://mbostock.github.com/d3/d3.js?2.1.3"></script>
    <script type="text/javascript" src="http://mbostock.github.com/d3/d3.geom.js?2.1.3"></script>
    <script type="text/javascript" src="http://mbostock.github.com/d3/d3.layout.js?2.1.3"></script>

    <style type="text/css">
    .slice text {
        font-size: 16pt;
        font-family: Arial;
    }   
    </style>
</head>
<body>
    <button id="button"> Test </button>
    <br>
    <form id="controls">
        <div>
            <h2>Y axis</h2>
            <ul id="y-axis">
                <li><label><input checked="checked" type="radio" name="y-axis" value="Component">Component</label></li>
                <li><label><input type="radio" name="y-axis" value="Browser">Browser</label></li>
                <li><label><input type="radio" name="y-axis" value="Version">Version</label></li>
            </ul>
        </div>
    </form>
    <script type="text/javascript">
    // return a list of types which are currently selected
    function plottableTypes () {
        var types = [].map.call (document.querySelectorAll ("#coaster-types input:checked"), function (checkbox) { return checkbox.value;} );
        return types;
     }


    var w = 600,                        //width
    h = 600,                            //height
    r = 100,
    r2 = 200,                           //radius
    axis = getAxis (),                  //axes
    color = d3.scale.category20c();     //builtin range of colors

    data = [
        {"Browser":"Internet Explorer ","Version":"8.0","Toatl":2000,"Component":"6077447412293130422"},
        {"Browser":"Internet Explorer ","Version":"9.0 ","Toatl":1852,"Component":"6077447412293130422"},
        {"Browser":"Internet Explorer ","Version":"6.0 ","Toatl":1754,"Component":"6077447412293130422"},
        {"Browser":"Firefox ","Version":"16.0 ","Toatl":1020,"Component":"6077447412293130422"},
        {"Browser":"Chrome ","Version":"23.0 ","Toatl":972,"Component":"6077447412293130422"},
        {"Browser":"Internet Explorer ","Version":"7.0 ","Toatl":700,"Component":"6077447412293130422"},
        {"Browser":"Mobile Safari ","Version":"6.0 ","Toatl":632,"Component":"6077447412293130422"},
        {"Browser":"BOT ","Version":"BOT ","Toatl":356,"Component":"6077447412293130422"},
        {"Browser":"Firefox ","Version":"8.0 ","Toatl":196,"Component":"6077447412293130422"},
        {"Browser":"Mobile Safari ","Version":"5.1 ","Toatl":184,"Component":"6077447412293130422"}
    ];

    var vis = d3.select("body")
        .append("svg:svg")              //create the SVG element inside the <body>
        .data([data])                   //associate our data with the document
        .attr("width", w)           //set the width and height of our visualization (these will be attributes of the <svg> tag
        .attr("height", h)
        .append("svg:g")                //make a group to hold our pie chart
        .attr("transform", "translate(" + r2 + "," + r2 + ")")    //move the center of the pie chart from 0, 0 to radius, radius

    var arc = d3.svg.arc()              //this will create <path> elements for us using arc data
        .outerRadius(r);


    var pie = d3.layout.pie()           //this will create arc data for us given a list of values
        .value(function(d) { return d.Toatl; });    //we must tell it out to access the value of each element in our data array

    var arcs = vis.selectAll("g.slice")     //this selects all <g> elements with class slice (there aren't any yet)
        .data(pie)                          //associate the generated pie data (an array of arcs, each having startAngle, endAngle and value properties) 
        .enter()                            //this will create <g> elements for every "extra" data element that should be associated with a selection. The result is creating a <g> for every object in the data array
        .append("svg:g")                //create a group to hold each slice (we will have a <path> and a <text> element associated with each slice)
        .attr("class", "slice");    //allow us to style things in the slices (like text)


    arcs.append("svg:path")
        .attr("fill", function(d, i) { return color(i); } ) //set the color for each slice to be chosen from the color function defined above
        .attr("d", arc);                                    //this creates the actual SVG path using the associated data (pie) with the arc drawing function


    arcs.append("svg:text")                                     //add a label to each slice
        .attr("transform", function(d) {                    //set the label's origin to the center of the arc
            //we have to make sure to set these before calling arc.centroid
            d.innerRadius = r2;
            d.outerRadius = r;
            return "translate(" + arc.centroid(d) + ")";        //this gives us a pair of coordinates like [50, 50]
        })
        .attr("text-anchor", "middle")                          //center the text on it's origin
        .text(function(d, i) { 
            if(axis.yAxis == "Component"){
                return data[i].Component;
            }
            return data[i].Browser;     //get the label from our original data array
        });       

        d3.select('#button').on('click', reColor);

        var arcOver = d3.svg.arc()
            .outerRadius(r + 30) 
            .innerRadius(0);
        var arc = d3.svg.arc()
            .outerRadius(r)
            .innerRadius(0); 

        var arcs = vis.selectAll("g.slice")
            .attr("class", "slice")
            .on("mouseover", function(d) {
                getAxis();
                d3.select(this)
                    .select("path")
                    .transition()
                    .duration(500)
                .attr("d", arcOver);
                d3.select(this).select("text")
                    .text(function(d, i) { 
                        if(axis.yAxis == "Component"){
                            return data[i].Component;
                        }
                    return data[i].Browser;     //get the label from our original data array
                });       
            })
            .on("mouseout", function(d) {
                getAxis();
                d3.select(this)
                    .select("path")
                    .transition()
                    .duration(500)
                    .attr("d", arc);
                d3.select(this)
                    .select("text")
                    .text(function(d, i) { 
                        if(axis.yAxis == "Component"){
                            return data[i].Component;
                        }
                        return data[i].Browser;     //get the label from our original data array
                    }); 
                });


        function reColor(){
            var slices = d3.select('body').selectAll('path');
            slices.transition()
                .duration(2000)
                .attr("fill", function(d, i) { return color(i+2); } );
            slices.transition()
                .delay(2000)
                .duration(2000)
                .attr("fill", function(d, i) { return color(i+10); } )
        }
        function makeData(){

        }
        // return an object containing the currently selected axis choices
        function getAxis () {
            var y = document.querySelector("#y-axis input:checked").value;
            return {
                yAxis: y,
            };
        }
        function update() {
            axis = getAxis()
            arcs.selectAll("text")          //add a label to each slice              
                .text(function(d, i) { 
                    if(axis.yAxis == "Component"){
                        return data[i].Component;
                    }
                    return data[i].Browser;     //get the label from our original data array
                });
            }

        document.getElementById("controls").addEventListener ("click", update, false);
        document.getElementById("controls").addEventListener ("keyup", update, false);
    </script>
</body>
</html>
4

3 回答 3

3

正如其他人在介绍帖子的评论中提到的那样,可以实现像您描述的那样的解决方案,并且可以使用您的代码加上“移动标签”示例的一部分。如果我理解正确,您希望使用 force-layout 实现不重叠的标签,这是一个非常好的主意,我还没有偶然发现。

您从示例中粘贴的代码部分仅绘制您已经正确解释的标签和线条。下一步是在饼图周围以类似力的布局重新排列标签。

示例中重新排列标签(和链接)的部分如下:

function redrawLabels() {
    labelBox
        .attr("transform",function(d) { return "translate("+d.labelPos.x+" "+d.labelPos.y+")"})

    links
        .attr("x1",function(d) { return d.anchorPos.x})
        .attr("y1",function(d) { return d.anchorPos.y})
        .attr("x2",function(d) { return d.labelPos.x})
        .attr("y2",function(d) { return d.labelPos.y})
}        

// Initialize the label-forces
labelForce = d3.force_labels()
    .linkDistance(0.0)
    .gravity(0)
    .nodes([]).links([])
    .charge(-60)
    .on("tick",redrawLabels)

该功能是改变标签和线条位置的功能。力由 D3 计算并以d3.force_labels().... 如您所见,该函数被分配为滴答事件的事件处理程序。换句话说:在计算力的每一步之后,D3 为每个标签调用“抽屉”并更新位置。

不幸的是,我对 D3 的方法不是很熟悉force_labels(),但我认为它的工作原理与常规的force(). 在您的情况下,锚点放置在每个标签的每个饼片中的某个位置。每个馅饼块(而不是馅饼本身)的中心越多越好。不幸的是,您必须以某种方式计算此锚位置(sin 和 cos 的东西)并将行端设置为redrawLabels().

完成此操作后,您将看到第一个结果。您可能必须使用重力、linkDistance 等力值来获得良好的效果。(这就是示例中的 silders 所做的。)

有关更多信息,请参阅 d3 文档:https ://github.com/mbostock/d3/wiki/Force-Layout

然后你可能会偶然发现标签在饼图周围排列但没有重叠但以某种奇怪的顺序排列的问题。您可以通过最初将标签以正确的顺序放置在饼图周围的较大圆圈上来解决此问题,而不是将它们随机放置在面板周围,这是问题的原因。这样,您将体验到更少的抖动和错位。

这个想法也在另一个块示例中进行了描述:http: //bl.ocks.org/mbostock/7881887

在此示例中,节点最初放置在虚拟圆上。定位由以下函数计算:

x: Math.cos(i / m * 2 * Math.PI) * 200 + 宽度 / 2 + Math.random(),

y: Math.sin(i / m * 2 * Math.PI) * 200 + height / 2 + Math.random()

它们代表一个半径为 200 的圆,位于绘图面板的中心。圆被分成m个同样大的块。i/m 只计算 i 范围从 0 到 m-1 的“块位置”。

希望我能帮上忙!

于 2014-09-13T00:30:56.770 回答
0

是的,您绝对可以将力标签与饼图结合起来!您开始使用的饼图标签没有什么特别之处,它们只是文本元素,可以像其他任何东西一样使用变换或 x/y 定位。看起来您最初是根据它们标记的弧的质心来定位这些标签,但您可以轻松地使用其他标准(例如力布局的输出)。

D3 的力布局根据一组约束来计算事物的位置,这些约束包括什么是固定的、什么是可移动的以及哪些与哪些相连。Mike 的 bl.ocks 示例中的labelForce.update方法用于告知力布局需要定位多少对象,以及固定“锚”点在哪里。然后它将标签的计算位置保存到图表的数据模型中,稍后在redrawLabels函数中使用它们。

于 2014-03-24T08:31:57.200 回答
-1

您需要创建两条弧线。一个用于绘制饼图,另一个用于放置标签。

// first arc used for drawing the pie chart
var arc = d3.svg.arc()
  .outerRadius(radius - 10)
  .innerRadius(0);

// label attached to first arc
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; });

// second arc for labels
var arc2 = d3.svg.arc()
  .outerRadius(radius + 20)
  .innerRadius(radius + 20);

// label attached to second arc
g.append("text")
  .attr("transform", function(d) { return "translate(" + arc2.centroid(d) + ")"; })
  .attr("dy", ".35em")
  .style("text-anchor", "middle")
  .text(function(d) { return d.data.age; });
于 2013-10-22T22:14:50.580 回答