Links overlapping with the image in directional force layout

In my directional force layout codepen I am using icons rather than circles; currently, the links are overlapping with the image/icon. I want the links to end just before the image. However I see in d3noob's example the link starts/end after the circle, it does not overlap with the circle.

I tweaked the following return statement but not able to fix it properly.

function tick() {
 link.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    );
 });

 node.attr("transform", function(d) {
    return "translate(" + d.x + "," + d.y + ")";
 });
}

Answers:

Answer

The mathematical way to avoid this, without playing with the elements' order, is computing an offset, so the links start and end at the nodes' edges.

In your case, we first get the angle between the two nodes:

var angle = Math.atan2(dx, dy);

Then we compute the offset:

var offsetX = radius * Math.cos(angle);
var offsetY = radius * Math.sin(angle);

And finally use that value in the d attribute:

return ("M" + (d.source.x + offsetX) + "," + (d.source.y + offsetY) +
  "A" + dr + "," + dr + " 0 0,1 " + (d.target.x - offsetX) +
  "," + (d.target.y - offsetY)
);

Here is the code with that change:

var width = 500,
  height = 400,
    radius = 8;

var fill = d3.scale.category20();
var links = [{ source:  "FH", target: "TP" }];
var nodes = [
    { id: "FH", x: 100, y: 110 },
    { id: "TP", x: 200, y: 110 },
    { id: "GW", x: 200, y: 110 },
    { id: "DB", x: 100, y: 110 }
  ]

var map = {}
nodes.forEach(function(d,i){
  map[d.id] = i;
})

links.forEach(function(d) {
  d.source = map[d.source];
  d.target = map[d.target];
})

var force = d3.layout
  .force()
  .size([width, height])
  .nodes(nodes)
  .links(links)
  .linkDistance(150)
  .charge(-500)
  .on("tick", tick);

var svg = d3
  .select("body")
  .append("svg")
  .attr("width", width)
  .attr("height", height);

var arrows = svg
  .append("svg:defs")
  .selectAll("marker")
  .data(["arrow"])
  .enter()
  .append("marker")
  .attr("id", String)
  .attr("viewBox", "0 -5 10 10")
  .attr("refX", 10)
  .attr("refY", 0)
  .attr("markerWidth", 6)
  .attr("markerHeight", 6)
  .attr("orient", "auto")
  .append("path")
  .attr("d", "M0,-5L10,0L0,5");

svg
  .append("rect")
  .attr("width", width)
  .attr("height", height);

var nodes = force.nodes(),
  links = force.links(),
  node = svg.selectAll(".node"),
  link = svg.selectAll(".link");

restart();





function tick() {
  link.attr("d", function(d) {
    var dx = d.target.x - d.source.x,
      dy = d.target.y - d.source.y;
    var angle = Math.atan2(dy, dx);
    var offsetX = radius * Math.cos(angle);
    var offsetY = radius * Math.sin(angle);
      dr = Math.sqrt(dx * dx + dy * dy);
    return (
      "M" +
      (d.source.x + offsetX) +
      "," +
      (d.source.y + offsetY) +
      "A" +
      dr +
      "," +
      dr +
      " 0 0,1 " +
      (d.target.x - offsetX) +
      "," +
      (d.target.y - offsetY)
    );
  });

  node.attr("transform", function(d) {
    return "translate(" + d.x + "," + d.y + ")";
  });
}

function restart() {
   link = link.data(links);
  link
    .enter()
    .append("path")
    .attr("class", "link")
    .attr("marker-end", "url(#arrow)");
  link.exit().remove();
  
  node = node.data(nodes);
  node
    .enter()
    .insert("g")
    .attr("class", "node")
    .call(force.drag);
  node
    .append("image")
    .attr("xlink:href", "https://github.com/favicon.ico")
    .attr("x", -8)
    .attr("y", -8)
    .attr("width", 16)
    .attr("height", 16);
  node
    .append("text")
    .attr("dx", 12)
    .attr("dy", ".35em")
    .text(function(d) {
      return d.id;
    });
  node.exit().remove();

   force.start();
}
#nodeConsole {
  width: 80%;
  height: 1px;
  font-family: courier new;
  padding: 1px;
  border: 3px solid gray;
  margin-top: 1px;
  overflow: autao;
}

#linkedNodes {
  width: 80%;
  font-family: courier new;
  padding: 10px;
}

#srcNodes {
  width: 40%;
  font-family: courier new;
  padding: 8px;
}

#targetNodes {
  width: 40%;
  font-family: courier new;
  padding: 8px;
}

rect {
  fill: none;
  pointer-events: all;
}

.node {
  fill: #000;
}

.cursor {
  fill: none;
  stroke: brown;
  pointer-events: none;
}

.link {
  stroke: #999;
}
.node text {
  pointer-events: none;
  font: 10px sans-serif;
}

path.link {
  fill: none;
  stroke: #666;
  stroke-width: 1.5px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>

Answer

In the restart() function draw the links before the nodes and use an image that has no internal transparency.

function restart() {
  link = link.data(links);
  link
    .enter()
    .append("path")
    .attr("class", "link")
    .attr("marker-end", "url(#arrow)");
  link.exit().remove();
  node = node.data(nodes);
  node
    .enter()
    .insert("g")
    .attr("class", "node")
    .call(force.drag);
  node
    .append("image")
    .attr("xlink:href", "https://github.com/favicon.ico")
    .attr("x", -8)
    .attr("y", -8)
    .attr("width", 16)
    .attr("height", 16);
  node
    .append("text")
    .attr("dx", 12)
    .attr("dy", ".35em")
    .text(function(d) {
      return d.id;
    });
  node.exit().remove();

  force.start();
}

Tags

Recent Questions

Top Questions

Home Tags Terms of Service Privacy Policy DMCA Contact Us Javascript

©2020 All rights reserved.