2017-01-06 2 views
0

Ich erstellte Force-Layout in d3v4. Markers hinzugefügt auf Links zur Darstellung der Richtung wie in Jfiddle https://jsfiddle.net/rjyk72ea/ gezeigt Anforderung sind Pfeile sollten Knoten zu berühren, aber Arrows versteckt unter Knoten (teilweise oder vollständig), wenn die Verbindung in diagonale Richtung des Knotens. Wie dieses Problem zu lösen?Pfeile berühren Knoten in d3.js nicht

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

Benötigen Sie wirklich rechteckigen Knoten? Werden kreisförmige Knoten ausreichen? – Assan

+0

Ja, ich möchte es für rechteckige Knoten lösen, da ich rechteckige Bilder mit derselben Größe wie Rechtecke in meinem Projekt verwende. – user6821214

+0

Interessante Frage. Ich habe vor einiger Zeit etwas Ähnliches für Kreise geantwortet (http://stackoverflow.com/a/41229068/16363). – Mark

Antwort

3

Ihre gerundeten Quadrate sind problematisch. Sie haben zwei Möglichkeiten: Behandeln Sie sie als Kreise und tun Sie as I did in this question. Oder behandle sie als Rechtecke (Quadrate) und finde den Schnittpunkt mit dem Quadrat. Da ich bereits eine vorherige Antwort für Kreise gegeben habe, lassen Sie uns über das Quadrat (Rechteck)/die Linienkreuzung sprechen.

Nun werde ich nicht ins Detail der Mathematik hinter Rechteck/Linie Schnittpunkt gehen (es gibt viele Ressourcen eine Google-Suche weg für diese), so lass uns mit der Funktion von this great answer beginnen (es verdient viel mehr upvotes) und wende es auf deine Frage an.

Zuerst ändere ich Ihren Link-Code, um mit einem Svg path anstelle von line zu arbeiten. Einfach sauberer und einfacher meiner Meinung nach. Mit der Funktion ich darüber verknüpft wird dann so einfach wie:

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

    .... 

Hier ist die volle Lauf Code:

<!DOCTYPE html> 
 
<html> 
 

 
<head> 
 
    <script data-require="[email protected]" 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>

+0

Ja, das funktioniert für mich und hilfreiche Antwort. – user6821214

+0

Können Sie bitte vorschlagen, wie das gleiche Problem für den Baum in d3 – user6821214

+0

@ user6821214 zu lösen, das klingt wie eine neue Frage. Schließe diese aus (klicke auf das "Häkchen" neben meiner Antwort und öffne ein neues). – Mark