箭头不接触d3.js中的节点 [英] Arrows are not touching to nodes in d3.js

查看:239
本文介绍了箭头不接触d3.js中的节点的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我在d3v4中创建了强制布局。在代表方向的链接上添加的标记如Jfiddle所示 https://jsfiddle.net/rjyk72ea/ < br>
要求是箭头应该触及节点,但是当链接在节点的对角方向时,箭头隐藏在节点下(部分或完全)。如何解决这个问题?

I created force layout in d3v4. Markers added on links to represent direction as shown in given Jfiddle https://jsfiddle.net/rjyk72ea/
Requirement is arrows should be touching to node but Arrows hides below node(partially or completely) when link is in diagonal direction of node.how to resolve this issue?

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


推荐答案

您的圆角方块有问题。您有两种选择:将他们视为圈子,然后按照我在此问题中的说法。或者将它们视为矩形(正方形),并找到与正方形的交点。因为,我已经给出了以前的回答为圆,让我们谈论正方形(矩形)/线交叉。

Your rounded squares are problematic. You have two choices: treat them as circles and do as I did in this question. Or treat them as rectangles (squares) and find the intersection point with the square. Since, I've already given a previous answer for circles, let's talk about square (rectangle)/line intersection.

现在,我不会深入到矩形/线交叉的数学的细节(有很多资源,google搜索),所以让我们开始该功能来自这个伟大的答案(它值得更多的upvotes),并将其应用于您的问题。

Now, I won't go into the detail of the math behind rectangle/line intersection (there's lots of resources a google search away for that), so let's start with the function from this great answer (it deserves way more upvotes) and apply it to your question.

首先,我改变你的链接代码以使用svg 路径而不是。只是更清洁和更容易在我看来。使用我在上面链接的函数,然后变得像下面这样容易:

First, I change your links code to work with an svg path instead of a line. Just cleaner and easier in my opinion. Using the function I linked above this then becomes as easy as:

 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="http://stackoverflow.com/a/31254199/253468">source</a>
       * @see <a href="http://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>

这篇关于箭头不接触d3.js中的节点的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

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