2013-01-11 78 views
13

我正在创建一个使用D3浮动标签的饼图。我是D3新手,我甚至不确定这是可能的吗?你可以以某种方式使用另一张图的标签吗?如果可以的话,你能指点我一个例子吗?D3 - 饼图和力导向标签

较短的说明: 我想从标签: http://bl.ocks.org/1691430

enter image description here ...是一个饼图上。

这是我在下面运行的代码: 或者在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> 
+0

是的,这是可能的。你发布的例子已经非常接近我认为你想要做的事情。你可以发布一些你已经尝试过的代码吗? –

+0

我很困惑,因为当我需要调用标签时,我会在上面的饼图中进行编辑。什么是call(labelForce.update)正在做什么? 感谢您的帮助! – gbam

+1

这个例子中的'锚'是被标记的东西。那些将是你的馅饼细分市场。在开始绘制饼图时可能会比较容易,并且可以使用示例中的代码作为饼图片段内某些点的数据。 –

回答

-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; }); 
0

是的,你绝对可以将力标与饼图结合起来!开始的饼图标签没有特别的特殊之处,它们只是文本元素,可以像使用transform或x/y的其他任何东西一样定位。看起来你最初是根据它们标注的圆弧的质心定位这些标签,但是你可以很容易地使用另一个标准(比如力布局的输出)。

D3的力布局基于一组关于什么是固定的,什么是可移动的以及哪些与哪些连接的约束来计算物体的位置。 Mike的bl.ocks示例中的labelForce.update方法正在用于通知强制布局需要定位多少个对象以及固定“锚点”的位置。然后它将标签的计算位置保存到图表的数据模型中,稍后将在redrawlabel函数中使用它们。

2

正如其他人在评论中提到的对您的介绍文章所述,您可以实现像您描述的解决方案,并且可以使用您的代码加上“移动标签”部分 - 例如。如果我理解正确,你想用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()...开始。如您所见,该函数被分配为tick-event的事件处理程序。换句话说:在计算力的每一步之后,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:数学。 sin(i/m * 2 * Math.PI)* 200 + height/2 + Math.random()

它们表示半径为200的圆,位于绘图面板的中心。这个圆被分成m个同样大的部分。我/米只是计算'piece-positions',其中我的范围从0到m-1。

希望我能帮上忙!