D3js可折叠力布局与有向路径?

时间:2013-05-05 10:55:07

标签: graph svg d3.js

我正在尝试执行强制定向布局,其中链接是指向节点的箭头(如显示herehere中所示的示例),并且其中有子节点的节点也是可折叠的(如Mike Bostock的例子所示:herehere)。

到目前为止折叠节点工作正常,但我无法理解箭头如何包含在路径中。以下是基于以上示例的代码的一部分:

force.nodes(nodes)
    .links(links)
    .gravity(0.05)
    .charge(-1500)
    .linkDistance(100)
    .friction(0.5)
    .linkStrength(function(l, i) {return 1 })
    .size([w, h])
    .start();

 // Append markers
 vis.append("svg:defs").selectAll("marker")
    .data(["end"])
  .enter().append("svg:marker")
    .attr("id", String)
    .attr("viewBox", "0 -5 10 10")
    .attr("refX", 15)
    .attr("refY", -1.5)
    .attr("markerWidth", 6)
    .attr("markerHeight", 6)
    .attr("orient", "auto")
 //.append("svg:path")      // <-- I not sure what this does
  //.attr("d", "M0,-5L10,0L0,5");


var path = vis.selectAll("path")
    .data(force.links());


// Enter new paths
path.enter().insert("svg:path")
    .attr("class", "link")
    .attr("marker-end", "url(#end)")
    .style("stroke", "#ccc");


// Exit any old paths.
path.exit().remove();


// Update the nodes…
var node = vis.selectAll("g.node")
    .data(nodes, function(d) { return d.id; })

node.select("circle")
    .style("fill", color);

// Enter any new nodes.
var nodeEnter = node.enter().append("svg:g")
    .attr("class", "node")
    .attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; })
    .on("click", click)
    .call(force.drag);


//Add an immage to the node
nodeEnter.append("svg:image")
      .attr("xlink:href",  function(d) { return d.image;})
      .attr("x", function(d) { return (0 - Math.sqrt(d.size)) / 10 || 4.5;})
      .attr("y", function(d) { return (0 - Math.sqrt(d.size)) / 10 || 4.5;})
      .attr("height", 16)
      .attr("width", 16);


// Exit any old nodes.
node.exit().remove();

// Re-select for update.
node = vis.selectAll("g.node");
path = vis.selectAll("path")

force.on("tick", function() {
     // Draw curved links
     path.attr("d", function(d) {
    var dx = d.target.x - d.source.x,
        dy = d.target.y - d.source.y,
        dr = Math.sqrt(dx * dx + dy * dy);
    return "M" + d.source.x + "," 
            + d.source.y 
            + "A" + dr + "," 
            + dr + " 0 0,1 " 
            + d.target.x + "," 
            + d.target.y;
     });

     node.attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; });
});

我的理解是下面的代码片段负责绘制箭头,指定箭头应指向的块(例如.data(["end"])

 vis.append("svg:defs").selectAll("marker")
     .data(["end"])
   .enter().append("svg:marker")
     .attr("id", String)
     .attr("viewBox", "0 -5 10 10")
     .attr("refX", 15)
     .attr("refY", -1.5)
     .attr("markerWidth", 6)
     .attr("markerHeight", 6)
     .attr("orient", "auto")
   .append("svg:path")      
     .attr("d", "M0,-5L10,0L0,5");

然后在输入路径时引用它(即.attr("marker-end", "url(#end)");)。

但我可能会遗漏一些东西,因为在我的图表中显示了路径,但没有显示箭头。

感谢您的帮助!

1 个答案:

答案 0 :(得分:0)

我找到了“几乎”正常工作的解决方案。以下是完整的代码,以及对底部仍然失败的简短说明:

    var w = 1280,
        h = 800,
        root,
                    vis;

    var force = d3.layout.force()
                .gravity(200)
                .charge(-1500)
                .linkDistance(100)
                .friction(0.01)
                .size([w, h])
                ;


    $(document).ready(function() {

        var newHeight = '100%';

        $("#svgdiv").html("<svg id='graph' xmlns='http://www.w3.org/2000/svg' width='100%' height='100%'></svg>");

        vis = d3.select("svg");

        d3.json("../json/flare.json", function(json) {
            root = json;
            root.fixed = true;
            root.x = w / 2;
            root.y = h / 2;


        // Build the arrow
        var defs = vis.insert("svg:defs").selectAll("marker")
            .data(["end"]);

          defs.enter().append("svg:marker")
            .attr("id", String)
            .attr("viewBox", "0 -5 15 15")
            .attr("refX", 15)
            .attr("refY", -1.5)
            .attr("markerWidth", 6)
            .attr("markerHeight", 6)
            .attr("orient", "auto")
          .append("svg:path")
            .attr("d", "M0,-5L10,0L0,5");       

            update();
        });
    });



    /**
     * 
     */
    function update() {
      var nodes = flatten(root),
          links = d3.layout.tree().links(nodes);

      // Restart the force layout.
      force.nodes(nodes)
            .links(links)
            .gravity(0.05)
            .charge(-1500)
            .linkDistance(100)
            .friction(0.5)
            .linkStrength(function(l, i) {return 1 })
            .size([w, h])
            .start();


        var path = vis.selectAll("path.link") // <-- THIS WAS CHANGED TO "path.links"
            .data(links, function(d) { return d.target.id; });

          path.enter().insert("svg:path")
            .attr("class", "link")
            .attr("marker-end", "url(#end)")
            .style("stroke", "#ccc");


      // Exit any old paths.
      path.exit().remove();


      // Update the nodes…
      var node = vis.selectAll("g.node")
          .data(nodes, function(d) { return d.id; });

      // Enter any new nodes.
      var nodeEnter = node.enter().insert("svg:g")
          .attr("class", "node")
          .attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; })
          .on("click", click)
          .call(force.drag);


        node.select("circle")
          .style("fill", color);


      nodeEnter.append("svg:circle")
          .attr("r", function(d) { return Math.sqrt(d.size) / 10 || 4.5; })
          .style("fill", color);

      // Add text to the node (as defined by the json file) 
      nodeEnter.append("svg:text")
          .attr("text-anchor", "middle")
          .attr("dx", function(d) { return Math.sqrt(d.size) / 10 || 4.5; })
          .attr("dy", ".35em")
          .text(function(d) { return d.name; });
      /* */

      //Add an image to the node
      nodeEnter.append("svg:image")
              .attr("xlink:href",  function(d) { return d.logo;})
              .attr("x", function(d) { return (0 - Math.sqrt(d.size)) / 10 || 4.5;})
              .attr("y", function(d) { return (0 - Math.sqrt(d.size)) / 10 || 4.5;})
              .attr("height", 16)
              .attr("width", 16);
       /* */    


      // Exit any old nodes.
      node.exit().remove();

      // Re-select for update.
      node = vis.selectAll("g.node");
      path = vis.selectAll("path.link");   // <-- THIS WAS CHANGED TO "path.link"


      force.on("tick", function() {


        path.attr("d", function(d) {
            var dx = d.target.x - d.source.x,
                dy = d.target.y - d.source.y,
                dr = Math.sqrt(dx * dx + dy * dy); 
            return  "M" + d.source.x + "," 
                        + d.source.y 
                        + "A" + dr + "," 
                        + dr + " 0 0,1 " 
                        + d.target.x + "," 
                        + d.target.y;
        });


        node.attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; });

      });

    }


    // Color leaf nodes orange, and packages white or blue.
    function color(d) {
      return d._children ? "#3182bd" : d.children ? "#c6dbef" : "#fd8d3c";
    }

    // Toggle children on click.
    function click(d) {
      if (d.children) {
        d._children = d.children;
        d.children = null;
      } else {
        d.children = d._children;
        d._children = null;
      }

      update();
    }



    // Returns a list of all nodes under the root.
    function flatten(root) {
      var nodes = []; 
      var i = 0;

      function recurse(node) {
        if (node.children) 
            node.children.forEach(recurse);
        if (!node.id) 
            node.id = ++i;
        nodes.push(node);
      }

      recurse(root);
      return nodes;
    }

我认为箭头未显示的原因之一是我在路径中给了一个类,如path.enter().insert("svg:path").attr("class", "link"),但是当选择路径时我没有正确引用它,因此它没有画出它 (即我有:

  var path = vis.selectAll("path")  
应该在哪里:

  var path = vis.selectAll("path.link").)

然后我还发现箭头标记的defs应该在update()函数之外定义,该函数在折叠和展开节点时调用。否则,每次单击节点时它都会将其附加到svg,这不是很有效。

所以现在节点崩溃onclick并绘制箭头(尽管它们很难看)。但是,还有一个问题让我很困惑:过了一段时间,没有任何明显的时间间隔或点击模式,图表冻结并在浏览器中调试Cannot read property 'target' of undefined。在“tick”函数中触发此错误,其中定义了弯曲路径:

    path.attr("d", function(d) {
        var dx = d.target.x - d.source.x,
            dy = d.target.y - d.source.y,
            dr = Math.sqrt(dx * dx + dy * dy); 
        return  "M" + d.source.x + "," 
                    + d.source.y 
                    + "A" + dr + "," 
                    + dr + " 0 0,1 " 
                    + d.target.x + "," 
                    + d.target.y;
    });

找不到d.target,因为在输入path = vis.selectAll("path.link");

之前,路径变量已重新启动(例如此force.on("tick", function() ...

奇怪的是,它在开始时起作用,并且它会在随机的时间突然停止工作! 所以,任何机构都知道可能发生的事情吗?

编辑:

我现在知道出了什么问题。出于某种原因,我使用的是我在某处找到的脚本d3.layout.js,我认为这是崩溃树所需要的。我删除了那个库,并使用了正常的d3.v3.js,一切都像它应该的那样...只是箭头是丑陋的。因此,上面的脚本应该有效,具有可折叠节点和有向路径。