Arrows do not touch nodes in d3.js

I created a power circuit in d3v4. Markers added links to represent directions, as shown in the above Jfiddle https://jsfiddle.net/rjyk72ea/
Requirement - the arrows must touch the node, but the arrows are hidden below the node (partially or completely) when the link is in the diagonal direction of the node. How to solve this problem?

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
Jan 06 '17 at 7:25
source share
1 answer

Your rounded squares are problematic. You have two options: treat them like circles and do as I did in this matter . Or consider them as rectangles (squares) and find the intersection point with the square. Since I already gave the previous answer for circles, let me talk about the square (rectangle) / intersection of the line.

Now I won’t go into the details of the math behind the rectangle / line intersection (there are many google search resources for this), so let's start with the function from this great answer (it deserves more attention) and apply it to your question.

Firstly, I change the link code to work with svg path instead of line . Just cleaner and simpler in my opinion. Using the function linked above, it becomes as simple 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; }); .... 

Here is the full code:

 <!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(" + dx + "," + dy + ")"; }); } function dragstarted(d) { if (!d3.event.active) simulation.alphaTarget(0.3).restart(); d.fx = dx; d.fy = dy; } 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> 
+3
Jan 6 '17 at 18:24
source share



All Articles