箭头不接触d3.js中的节点

时间:2017-01-06 07:25:37

标签: d3.js

我在d3v4中创建了力布局。标记添加了链接以表示方向,如给定的Jfiddle https://jsfiddle.net/rjyk72ea/所示 要求是箭头应该接触到节点但是当链接在node.how的对角线方向时,Arrows隐藏在节点下面(部分或完全)以解决此问题?

var mark = diagramLayout.append("svg:defs").selectAll("marker")//
    .data(["end"])      // Different link/path types can be defined here
    .enter().append("svg:marker")    // This section adds in the arrows
    .attr("id", String)
    .attr("viewBox", "0 -5 10 10")
    .attr("refX", markerRefx)
    .attr("refY", 0)
    .attr("markerWidth", 5)
    .attr("markerHeight", 5)
    .attr("orient", "auto")
    .attr("stroke", "#000")
    .attr("fill", "#000")
    .append("svg:path")
    .attr("d", "M0,-5L10,0L0,5")
    .style("stroke-width", "0.3px")
}

1 个答案:

答案 0 :(得分:4)

圆角正方形存在问题。您有两种选择:将它们视为圈子并执行as I did in this question。或者将它们视为矩形(正方形)并找到与正方形的交点。既然,我已经给出了圈子的先前答案,那么我们来谈谈方形(矩形)/线交叉。

现在,我不会详细了解矩形/直线交叉点背后的数学细节(谷歌搜索了大量资源),所以让我们从函数开始来自this great answer(它应该得到更多的赞成)并将其应用于你的问题。

首先,我更改您的链接代码以使用svg path而不是line。在我看来,更干净,更容易。使用我上面链接的功能,然后变得如此简单:

 function ticked(e) {

    link.attr("d", function(d) {

      var inter = pointOnRect(d.source.x, d.source.y,
        d.target.x - 20, d.target.y - 20,
        d.target.x + 20, d.target.y + 20);

      return "M" + d.source.x + "," + d.source.y + "L" + inter.x + "," + inter.y;
    });

    ....

这是完整运行的代码:



<!DOCTYPE html>
<html>

<head>
  <script data-require="d3@4.0.0" data-semver="4.0.0" src="https://d3js.org/d3.v4.min.js"></script>
  <style>
    .node {
      stroke: #fff;
      stroke-width: 1.5px;
    }
    
    .link {
      stroke: #000;
      stroke-opacity: .6;
    }
  </style>
</head>

<body>
  <div id="mainScreen" style="height:100%;width:100%;position:absolute;">
    <svg id="diagramLayout" style="height:100%;width:100%;position:absolute;"></svg>
  </div>
  <script>
    var width = 500;
    var height = 500;
    var nodeWidth = 40;
    var nodeHeight = 40;
    var circleRadius = 5;
    var diagramLayout;
    var graphData = {
      "nodes": [{
        "uid": "Term20",
        "name": "Term20",
        "image": "images/Term.png"
      }, {
        "uid": "glossforArrow",
        "name": "glossforArrow",
        "image": "images/Glossary.png"
      }, {
        "uid": "Term43",
        "name": "Term43",
        "image": "images/Term.png"
      }, {
        "uid": "Term1",
        "name": "Term43",
        "image": "images/Term.png"
      }, {
        "uid": "Term2",
        "name": "Term43",
        "image": "images/Term.png"
      }],
      "links": [{
        "source": "glossforArrow",
        "target": "Term20",
        "direction": "output",
        "label": "Owned Terms"
      }, {
        "source": "glossforArrow",
        "target": "Term43",
        "direction": "output",
        "label": "Owned Terms"
      }, {
        "source": "glossforArrow",
        "target": "Term1",
        "direction": "output",
        "label": "Owned Terms"
      }, {
        "source": "glossforArrow",
        "target": "Term2",
        "direction": "output",
        "label": "Owned Terms"
      }]
    };

    forceInitialize(graphData)


    function forceInitialize(graphData) {

      diagramLayout = d3.select("#diagramLayout")
        .attr("id", "diagramLayout") //set id
        .attr("width", width) //set width
        .attr("height", height) //set height
        .append("g")
        .attr("transform", "translate(" + 20 + "," + 20 + ")")

      markerRefx = 35;

      simulation = d3.forceSimulation();
      alphaMulti = 1;
      simulation.force("link", d3.forceLink().id(function(d) {
          return d.uid;
        }).distance(70).strength(0))
        .force("charge", d3.forceManyBody().distanceMin(20).distanceMax(50))
        .force("centre", d3.forceCenter(width / 2, height / 2))
        .force("x", d3.forceX(2))
        .force("y", d3.forceY(10))
        .force("collide", d3.forceCollide().radius(function(d) {
          return 80;
        }).iterations(2))
      simulation.on('end', function() {
        simulation.force("link", d3.forceLink().id(function(d) {
            return d.uid;
          }).distance(30).strength(0.0).iterations(10))
          .force("x", d3.forceX().strength(0))
          .force("y", d3.forceX().strength(0))
      });

      force(graphData);
    }


    //Force Layout
    function force(graphData) {

      var linkEnter = diagramLayout.selectAll(".links");
      linkEnter = linkEnter.data(graphData.links)
        .enter().append("g")
        .attr("class", "links")

      var link = linkEnter.append("path")
        .attr("stroke-width", function(d) {
          return Math.sqrt(2);
        })
        .attr("stroke-opacity", "0.3")
        .attr("stroke", "#000")

      graphData.links.forEach(function(d) {
        if (d.direction == "input") {
          var mark = diagramLayout.append("svg:defs").selectAll("marker") //
            .data(["start"]) // Different link/path types can be defined here
            .enter().append("svg:marker") // This section adds in the arrows
            .attr("id", String)
            .attr("viewBox", "0 -5 10 10")
            .attr("refX", 0)
            .attr("refY", 0)
            .attr("markerWidth", 5)
            .attr("markerHeight", 5)
            .attr("orient", "auto")
            .attr("stroke", "#000")
            .attr("fill", "#000")
            .append("svg:path")
            .attr("d", "M0,-5L10,0L0,5")
            .style("stroke-width", "0.3px")
            .attr("transform", "rotate(180,5, 0)");
        } else if (d.direction == "output") {
          var mark = diagramLayout.append("svg:defs").selectAll("marker") //
            .data(["end"]) // Different link/path types can be defined here
            .enter().append("svg:marker") // This section adds in the arrows
            .attr("id", String)
            .attr("viewBox", "0 -5 10 10")
            .attr("refX", 9)
            .attr("refY", 0)
            .attr("markerWidth", 5)
            .attr("markerHeight", 5)
            .attr("orient", "auto")
            .attr("stroke", "#000")
            .attr("fill", "#000")
            .append("svg:path")
            .attr("d", "M0,-5L10,0L0,5")
            .style("stroke-width", "0.3px")
        }
      });

      link.attr("marker-end", function(d) {
        if (d.direction === "input")
          return "";
        else
          return "url(#end)";
      })

      link.attr("marker-start", function(d) {
        if (d.direction === "input")
          return "url(#start)";
        else
          return "";
      })
      
      var node = diagramLayout.selectAll(".node");
      node = node.data(graphData.nodes, function(d) {
        return d.uid;
      });

      var nodeEnter = node.enter().append("g")
        .attr("class", "node")
        .attr("height", nodeHeight)
        .attr("width", nodeWidth)

      var nodeIcon = nodeEnter.append("rect")
        .attr("class", "rect")
        .attr("x", -20)
        .attr("y", -20)
        .attr("rx", 10)
        .attr("width", 40)
        .attr("height", 40)
        .attr("stroke-width", function(d) {
          return Math.sqrt(2);
        })
        .attr("stroke-opacity", "0.3")
        .attr("stroke", "#000")
        .attr("fill", "steelblue")
      nodeIcon.call(d3.drag()
        .on("start", dragstarted)
        .on("drag", dragged)
        .on("end", dragended));

      simulation
        .nodes(graphData.nodes)
        .on("tick", ticked);

      setTimeout(function tick() {
        simulation.tick();
        if (simulation.alpha() >= .005);
        setTimeout(tick, 0);
      }, 0);

      simulation.force("link")
        .links(graphData.links);
      simulation.restart();

      function ticked(e) {

        link.attr("d", function(d) {
          
          var inter = pointOnRect(d.source.x, d.source.y,
            d.target.x - 20, d.target.y - 20,
            d.target.x + 20, d.target.y + 20);
            
          return "M" + d.source.x + "," + d.source.y + "L" + inter.x + "," + inter.y;
        })
        

        nodeEnter.attr("transform", function(d) {
          d.fixed = true;
          return "translate(" + d.x + "," + d.y + ")";
        });
      }

      function dragstarted(d) {
        if (!d3.event.active) simulation.alphaTarget(0.3).restart();
        d.fx = d.x;
        d.fy = d.y;
      }

      function dragged(d) {
        d.fx = d3.event.x;
        d.fy = d3.event.y;
      }

      function dragended(d) {
        d3.select(this).classed("fixed", d.fixed = false);
        d3.selectAll(".node").fixed = true;
      }

      /**
       * Finds the intersection point between
       *     * the rectangle
       *       with parallel sides to the x and y axes 
       *     * the half-line pointing towards (x,y)
       *       originating from the middle of the rectangle
       *
       * Note: the function works given min[XY] <= max[XY],
       *       even though minY may not be the "top" of the rectangle
       *       because the coordinate system is flipped.
       *
       * @param (x,y):Number point to build the line segment from
       * @param minX:Number the "left" side of the rectangle
       * @param minY:Number the "top" side of the rectangle
       * @param maxX:Number the "right" side of the rectangle
       * @param maxY:Number the "bottom" side of the rectangle
       * @param check:boolean (optional) whether to treat point inside the rect as error
       * @return an object with x and y members for the intersection
       * @throws if check == true and (x,y) is inside the rectangle
       * @author TWiStErRob
       * @see <a href="https://stackoverflow.com/a/31254199/253468">source</a>
       * @see <a href="https://stackoverflow.com/a/18292964/253468">based on</a>
       */
      function pointOnRect(x, y, minX, minY, maxX, maxY, check) {
        //assert minX <= maxX;
        //assert minY <= maxY;
        if (check && (minX <= x && x <= maxX) && (minY <= y && y <= maxY))
          throw "Point " + [x, y] + "cannot be inside " + "the rectangle: " + [minX, minY] + " - " + [maxX, maxY] + ".";
        var midX = (minX + maxX) / 2;
        var midY = (minY + maxY) / 2;
        // if (midX - x == 0) -> m == ±Inf -> minYx/maxYx == x (because value / ±Inf = ±0)
        var m = (midY - y) / (midX - x);

        if (x <= midX) { // check "left" side
          var minXy = m * (minX - x) + y;
          if (minY <= minXy && minXy <= maxY)
            return {
              x: minX,
              y: minXy
            };
        }

        if (x >= midX) { // check "right" side
          var maxXy = m * (maxX - x) + y;
          if (minY <= maxXy && maxXy <= maxY)
            return {
              x: maxX,
              y: maxXy
            };
        }

        if (y <= midY) { // check "top" side
          var minYx = (minY - y) / m + x;
          if (minX <= minYx && minYx <= maxX)
            return {
              x: minYx,
              y: minY
            };
        }

        if (y >= midY) { // check "bottom" side
          var maxYx = (maxY - y) / m + x;
          if (minX <= maxYx && maxYx <= maxX)
            return {
              x: maxYx,
              y: maxY
            };
        }

        // Should never happen :) If it does, please tell me!
        throw "Cannot find intersection for " + [x, y] + " inside rectangle " + [minX, minY] + " - " + [maxX, maxY] + ".";
      }

    }
  </script>
</body>

</html>
&#13;
&#13;
&#13;