1

我正在尝试做一个强制导向的布局,其中链接是指向节点的箭头(如此处和此处所示的示例) 并且具有子节点的节点也是可折叠的(如 Mike Bostock 的示例所示:此处在这里)。

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

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)");)。

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

谢谢你的帮助!

4

1 回答 1

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 中,这不是很有效。

所以现在节点在点击时折叠并绘制箭头(虽然它们很丑)。但是,仍然有一个问题让我非常困惑:一段时间后,没有任何明显的时间间隔或点击模式,图形冻结并在浏览器中调试时显示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,一切都像它应该的那样工作......只是箭头很难看。因此,上面的脚本应该可以工作,具有可折叠的节点和定向路径。

于 2013-05-06T19:58:37.990 回答