d3.js sunburst,具有动态构建的可更新数据结构

时间:2016-05-18 14:39:39

标签: d3.js dynamic graph

我正在尝试使用d3.js创建一个完全动态的Sunburst图。 我找到的示例和教程倾向于使用现有/完全填充的数据结构,这些数据结构可能能够修改现有弧的值,但不允许根据需要添加子弧。

同样,我找到的教程允许新数据集只是替换现有结构并从头开始绘制。 这不是我试图实现的行为。

我需要的是基于提供的传入数据的动态构建图。

我可以将子项附加到数据集的末尾,转换并呈现​​结果而不会出现问题。每当我在现有结构中的某个地方插入一个孩子时,就会出现问题,d3的selectAll()没有按预期运行。它包括新的弧(尚未绘制),导致任何剩余的弧被错误地渲染。然后当转换弧时,它似乎得到弧Dom ID和它所代表的数据混合起来。新弧不可见,并且应存放新弧的空白空间。

要清楚我的意图是:

  1. 添加到现有数据结构,允许在提供新信息时添加新子项
  2. 在创建和绘制新弧之前,转换现有弧的开放空间
  3. 分为jsfiddle示例的四个步骤:

    1. 初始化图形(绘制一个不可见的“根”弧)

      {姓名:" a_0",儿童:[]}

    2. 将First Child数据及其子项添加到root

      {姓名:" a_0",儿童:[     {name:" a_1",children:[{name:" a_2",children:[{name:" a_3" }]}]} ]}

    3. 将第二个子项和基础子项添加到root

      {姓名:" a_0",儿童:[     {name:" a_1",children:[{name:" a_2",children:[{name:" a_3" }]}]},     {name:" a_4",children:[{name:" a_5",children:[{name:" a_6" }]}]} ]}

    4. 在现有弧a_2

      中插入另一个子项

      {姓名:" a_0",儿童:[     {姓名:" a_1",孩子:[         {姓名:" a_2",孩子:[             {姓名:" a_3" },                 {姓名:" a_7" }             ]}         ]},     {姓名:" a_4",孩子:[         {姓名:" a_5",孩子:[             {姓名:" a_6" }         ]}     ]} ]}

    5. 第1步可以正常使用

      第2步正确绘制弧

      第3步转换现有弧并将新弧添加到图中

      第4步会导致一些意想不到的行为。

      在现有和进入新弧线的过程中,一些弧线会跳跃到#34;失去与各自数据的正确关联 最终结果似乎是:

      • a_0 - 是正确的
      • a_1& a_2 - 看起来正确
      • a_3 - 缩小以容纳新兄弟a_7 - 预期行为
      • a_4 - 消失
      • a_5 - 跳转到a_4应该
      • 的位置
      • a_6 - (看起来像)它是重复的并且存在一次它应该在哪里以及a_5应该在哪里
      • a_7 - 未显示,应显示的位置为空格并且似乎与a_6数据相关

      最终结果如何以及实际发生的情况并不相同。

      • 在尝试更新图形时,现有弧线的selectAll()包括(a_0,a_1,a_2,a_3,a_4,a_5,a_7)。如果现有的a_6不包含在selectAll()中,但a_7(尚未绘制)是。
      • enter()函数似乎对现有的a_6进行操作,然后将其视为新的弧

      看起来我在正确的轨道上一路走到a_6,但是在添加a_7时我没有弄清楚这种行为的原因。

      jsFidde执行上述步骤,包括:

      • 每个弧的独特颜色
      • 显示每个弧名称的表格
      • 如果弧由d3js处理' selectAll()(即"现有")或输入()(即" new"),
      • 绘制现有或新弧时当前正在分配的d3索引。
      • 预期目标位置,其中每个弧应在任何过渡后出现,
      • 作为Arc的Arctween信息正在从之前的位置转换到新位置

      问题:

      • 这会导致第4步中的这种行为?
      • 有没有办法确保每个弧与它代表的数据之间的完整性?
      • 有没有办法将子项插入现有结构或更新此动态庄园中的图形?

      jsfiddle https://jsfiddle.net/mfitzgerald/j2eowwya/

      上的工作示例
      var dataObj = { name:"a_0", color: "none" };
      var height = 300;
      var width = 500;
      var radius = Math.min(width, height) / 2;
      var graph = d3.select("#graph")
          .attr('height', height)
          .attr('width', width)
          .append("g")
              .attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");
      
      var partition = d3.layout.partition()
                  .sort(null)
                  .size([2 * Math.PI, radius * radius])
                  .value(function(d, i) { return 1; });
      var arc = d3.svg.arc()
          .startAngle(function(d)  { if (isNaN(d.x))  { d.x = 0; }  d.x0 = d.x;   return d.x; })
          .endAngle(function(d)    { if (isNaN(d.dx)) { d.dx = 0; } d.dx0 = d.dx; return d.x + d.dx; })
          .innerRadius(function(d) { if (isNaN(d.y))  { d.y = 0; }  d.y0 = d.y;   return Math.sqrt(d.y); })
          .outerRadius(function(d) { if (isNaN(d.dy)) { d.dy = 0; } d.dy0 = d.dy; return Math.sqrt(d.y + d.dy); });
      
      var arcTween = function(a) {
          var i = d3.interpolate({x: a.x0, dx: a.dx0, y: a.y0, dy: a.dy0}, a);
          return function(t) {
              var b = i(t);
              a.x0 = b.x;
              a.dx0 = b.dx;
              a.y0 = b.y;
              a.dy0 = b.dy;
              displayStats("arctween", b);
              return arc(b);
          };
      }
      
      // Root Arc
      graph.datum(dataObj).selectAll('path.arc')
          .data(partition.nodes)
          .enter()
          .append('path')
              .attr('class', function(d) { return "arc " + d.name; })
              .attr("d", arc)
              .attr("id", function(d, i) { return "path_"+i; })
              .attr("name", function(d) { return d.name; })
              .style("fill", "none");
      
      
      function updateGraph() {
          console.log("Update Graph");
          console.log(dataObj);
      
          var update = graph.datum(dataObj).selectAll('path.arc')
                      .data(partition.nodes);
      
          // Move existing Arcs
          update.each(function(d, i) {
                  displayStats("target", d, i, "existing");
                  var domId = $(this).attr("id");
                  console.log("["+i+"] Exist Arc name:"+d.name+", dom_id:"+domId);
              })
              .transition()
                  .delay(function(d, i) { return i * 250; })
                  .duration(1500)
                  .attrTween("d", arcTween);
      
          // Add New Arcs
          update.enter().append('path')
              .attr('class', function(d, i) { return "arc "+d.name; })
              .attr("d", arc)
              .attr("id", function(d, i) {
                  var domId = "path_"+i;
                  console.log("["+i+"] NEW Arc name:"+d.name+", dom_id:"+domId);
                  displayStats("target", d, i, "new");
                  return domId;
              })
              .style("stroke", "#fff")
              .style("fill", function(d) { return d.color; })
              .style("opacity", 0)
              .transition()
                  .delay(function(d, i) { return i * 250; })
                  .duration(1500)
                  .style("opacity", .5)
                  .attrTween("d", arcTween);
      }
      

1 个答案:

答案 0 :(得分:0)

@Gordon回答了这个问题。在updateGraph代码中加入.data()时,通过添加key function解决了该问题。

jsfiddle

上的分叉示例
var update = graph.datum(dataObj).selectAll('path.arc')
            .data(partition.nodes, function(d) { return d.name; } );

我相信问题的答案是:

  • .data()函数使用索引数组,该数组仅在给定任何新弧附加到数组末尾的情况下唯一标识每个弧。一旦插入一个,这将导致数据,图形弧和相关的DOM ID未对齐。
  • 根据Gordon的建议,使用密钥功能可以对特定节点进行唯一标识,从而使数据和图表保持同步。

<强> 更新

需要进行额外的修改,因为DOM id是由数据元素的数组索引设置的,但仍然存在与DOM和基础图/数据的无效关联。

这将导致2 a_4 DOM id&#39; s。而不是使用节点名称作为DOM ID的数组索引应该保持这种关联正确。