在节点边缘对齐标记 D3 强制布局 [英] Align Marker on node edges D3 Force Layout

查看:25
本文介绍了在节点边缘对齐标记 D3 强制布局的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在尝试实现 d3 force 布局,但无法弄清楚如何以正确的方式定位链接的标记.

I'm trying to implement a d3 force layout and can't figure out how to position the markers of my links in a proper way.

这是我目前得到的:

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 + ")";
        }

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

It works properly to add different kind of markers for different links but if the nodes grow bigger (according to their weight) the markers are covered up by the nodes.

这是现在的屏幕截图:

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

Is there a way to position my markers exactly at the edge of the nodes?

推荐答案

有趣的问题.这是通过正常设置链接路径,然后通过将长度后退圆的半径来重新计算结束位置来实现的.

Interesting question. This works by setting the link path normally, then recalculating the end position by backing off the length by the radius of the circle.

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

First in the marker def, set the refX and refY to 0 (this is the current way it stays outside the circle):

  .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>

这篇关于在节点边缘对齐标记 D3 强制布局的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆