【问题标题】:Align Marker on node edges D3 Force Layout在节点边缘上对齐标记 D3 强制布局
【发布时间】:2017-05-04 17:59:11
【问题描述】:

我正在尝试实现 d3 强制布局,但不知道如何以正确的方式定位我的链接标记。

这是我目前得到的:

var links = force_data.force_directed_data.links;

        var nodes = {};

        // Compute the distinct nodes from the links.
        links.forEach(function (link) {
            link.source = nodes[link.source] || (nodes[link.source] = {name: link.source});
            link.target = nodes[link.target] || (nodes[link.target] = {name: link.target});
        });

        console.log(nodes);

        var width = 1000,
            height = 1000;

        var force = d3.layout.force()
            .nodes(d3.values(nodes))
            .links(links)
            .size([width, height])
            .linkDistance(300)
            .charge(-120)
            .friction(0.9)
            .on("tick", tick)
            .start();

        var svg = d3.select("#force-graph").append("svg")
            .attr("width", width)
            .attr("height", height);

        // Per-type markers, as they don't inherit styles.
        svg.append("defs").selectAll("marker")
            .data(["dominating"])
            .enter().append("marker")
            .attr("id", function (d) {
                return d;
            })
            .attr("viewBox", "0 -5 10 10")
            .attr("refX", 15)
            .attr("refY", -1.5)
            .attr("markerWidth", 12)
            .attr("markerHeight", 12)
            .attr("orient", "auto")
            .append("path")
            .attr("d", "M0,-5L10,0L0,5");

        svg.append("defs").selectAll("marker")
            .data(["concomidant"])
            .enter().append("marker")
            .attr("id", function (d) {
                return d;
            })
            .attr("viewBox", "0 -5 10 10")
            .attr("refX", 15)
            .attr("refY", -1.5)
            .attr("markerWidth", 12)
            .attr("markerHeight", 12)
            .attr("orient", "auto-start-reverse")
            .append("path")
            .attr("d", "M0,-5L10,0L0,5");

        var path = svg.append("g").selectAll("path")
            .data(force.links())
            .enter().append("path")
            .attr("class", function (d) {
                return "link " + d.type;
            })
            .attr("marker-end", function (d) {
                return "url(#" + d.type + ")";
            })
            .attr("marker-start", function (d) {
                if (d.type == "concomidant") {
                    return "url(#" + d.type + ")";
                }
            });


        var circle = svg.append("g").selectAll("circle")
            .data(force.nodes())
            .enter().append("circle")
            .attr("r", function (d) {
                return d.weight * 4;
            })
            .call(force.drag);

        var text = svg.append("g").selectAll("text")
            .data(force.nodes())
            .enter().append("text")
            .attr("x", 8)
            .attr("y", ".31em")
            .text(function (d) {
                return d.name;
            });

        // Use elliptical arc path segments to doubly-encode directionality.
        function tick() {
            path.attr("d", linkArc);
            circle.attr("transform", transform);
            text.attr("transform", transform);
        }

        function linkArc(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;
        }

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

为不同的链接添加不同类型的标记可以正常工作,但是如果节点变大(根据它们的权重),标记会被节点覆盖。

这是现在的截图:

有没有办法将我的标记准确地定位在节点的边缘?

【问题讨论】:

    标签: javascript d3.js force-layout


    【解决方案1】:

    有趣的问题。这通过正常设置链接路径,然后通过将长度回退到圆的半径来重新计算结束位置来工作。

    首先在标记 def 中,将 refXrefY 设置为 0(这是它留在圆圈外的当前方式):

      .attr("refX", 0)
      .attr("refY", 0)
    

    然后做:

    function tick() {
    
      // fit path like you've been doing
      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;
      });
    
      // recalculate and back off the distance
      path.attr("d", function(d) {
    
        // length of current path
        var pl = this.getTotalLength(),
            // radius of circle plus marker head
            r = (d.target.weight) * 4 + 16.97, //16.97 is the "size" of the marker Math.sqrt(12**2 + 12 **2)
            // position close to where path intercepts circle
            m = this.getPointAtLength(pl - r);          
    
         var dx = m.x - d.source.x,
            dy = m.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 " + m.x + "," + m.y;
      });
    
      circle.attr("transform", transform);
      text.attr("transform", transform);
    }
    

    运行代码:

    <!DOCTYPE html>
    <html>
    
    <head>
      <script data-require="d3@3.5.17" data-semver="3.5.17" src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.17/d3.js"></script>
      <style>
        path.link {
          fill: none;
          stroke: #666;
          stroke-width: 1.5px;
        }
        
        circle {
          fill: #ccc;
          stroke: #fff;
          stroke-width: 1.5px;
        }
        
        text {
          fill: #000;
          font: 10px sans-serif;
          pointer-events: none;
        }
      </style>
    </head>
    
    <body>
      <script>
        var links = [{
          "source": "Harry",
          "target": "Sally",
          "value": "1.2"
        }, {
          "source": "Harry",
          "target": "Mario",
          "value": "1.3"
        }, {
          "source": "Sarah",
          "target": "Alice",
          "value": "0.2"
        }, {
          "source": "Eveie",
          "target": "Alice",
          "value": "0.5"
        }, {
          "source": "Peter",
          "target": "Alice",
          "value": "1.6"
        }, {
          "source": "Mario",
          "target": "Alice",
          "value": "0.4"
        }, {
          "source": "James",
          "target": "Alice",
          "value": "0.6"
        }, {
          "source": "Harry",
          "target": "Carol",
          "value": "0.7"
        }, {
          "source": "Harry",
          "target": "Nicky",
          "value": "0.8"
        }, {
          "source": "Bobby",
          "target": "Frank",
          "value": "0.8"
        }, {
          "source": "Alice",
          "target": "Mario",
          "value": "0.7"
        }, {
          "source": "Harry",
          "target": "Alice",
          "value": "0.5"
        }, {
          "source": "Sarah",
          "target": "Alice",
          "value": "1.9"
        }, {
          "source": "Roger",
          "target": "Alice",
          "value": "1.1"
        }];
    
        var nodes = {};
    
        // Compute the distinct nodes from the links.
        links.forEach(function(link) {
          link.type = "dominating";
          link.source = nodes[link.source] || (nodes[link.source] = {
            name: link.source
          });
          link.target = nodes[link.target] || (nodes[link.target] = {
            name: link.target
          });
        });
    
        var width = 500,
          height = 500;
    
        var force = d3.layout.force()
          .nodes(d3.values(nodes))
          .links(links)
          .size([width, height])
          .linkDistance(300)
          .charge(-120)
          .friction(0.9)
          .on("tick", tick)
          .start();
    
        var svg = d3.select("body").append("svg")
          .attr("width", width)
          .attr("height", height);
    
        // Per-type markers, as they don't inherit styles.
        svg.append("defs").selectAll("marker")
          .data(["dominating"])
          .enter().append("marker")
          .attr("id", function(d) {
            return d;
          })
          .attr("viewBox", "0 -5 10 10")
          .attr("refX", 0)
          .attr("refY", 0)
          .attr("markerWidth", 12)
          .attr("markerHeight", 12)
          .attr("orient", "auto")
          .append("path")
          .attr("d", "M0,-5L10,0L0,5");
    
        svg.append("defs").selectAll("marker")
          .data(["concomidant"])
          .enter().append("marker")
          .attr("id", function(d) {
            return d;
          })
          .attr("viewBox", "0 -5 10 10")
          .attr("refX", 0)
          .attr("refY", 0)
          .attr("markerWidth", 12)
          .attr("markerHeight", 12)
          .attr("orient", "auto-start-reverse")
          .append("path")
          .attr("d", "M0,-5L10,0L0,5");
    
        var path = svg.append("g").selectAll("path")
          .data(force.links())
          .enter().append("path")
          .attr("class", function(d) {
            return "link " + d.type;
          })
          .attr("marker-end", function(d) {
            return "url(#" + d.type + ")";
          })
          .attr("marker-start", function(d) {
            if (d.type == "concomidant") {
              return "url(#" + d.type + ")";
            }
          });
    
    
        var circle = svg.append("g").selectAll("circle")
          .data(force.nodes())
          .enter().append("circle")
          .attr("r", function(d) {
            return d.weight * 4;
          })
          .call(force.drag);
    
        var text = svg.append("g").selectAll("text")
          .data(force.nodes())
          .enter().append("text")
          .attr("x", 8)
          .attr("y", ".31em")
          .text(function(d) {
            return d.name;
          });
    
        function 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;
          });
          path.attr("d", function(d) {
            var pl = this.getTotalLength(),
              r = (d.target.weight) * 4 + 16.97, //16.97 is the "size" of the marker Math.sqrt(12**2 + 12 **2)
              m = this.getPointAtLength(pl - r);
    
             var dx = m.x - d.source.x,
                dy = m.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 " + m.x + "," + m.y;
          });
          circle.attr("transform", transform);
          text.attr("transform", transform);
        }
        
    
        function transform(d) {
          return "translate(" + d.x + "," + d.y + ")";
        }
      </script>
    </body>
    
    </html>

    【讨论】:

    • 对于节点之间的直线链接,而不是弯曲的链接,您将如何做到这一点?
    • 您将如何使用多个路径来完成此操作,其中一个路径已将“路径”附加到名为“edgepaths”的变量中?
    • 我正在尝试添加这些代码,但收到此错误,“无法在 'SVGGeometryElement' 上执行 'getPointAtLength':提供的浮点值是非有限的。”
    猜你喜欢
    • 2013-08-21
    • 1970-01-01
    • 2014-04-29
    • 1970-01-01
    • 2014-10-18
    • 1970-01-01
    • 1970-01-01
    • 2014-06-30
    • 1970-01-01
    相关资源
    最近更新 更多