score:12

Accepted answer

Just to be clear for others, what we're talking about is a quadratic Bezier curve. That gives you a smooth curve between two points with one control point.

The basic method is:

  1. Find your A-B midpoint, call it J.
  2. Do some trig to find the point at the end of line segment M, call it K
  3. Use the SVG Q or T path commands to draw the quadratic Bezier curve, starting from A, going to B, with the control point K. (note that this won't look exactly like your diagram, but that can be tuned by changing the length of M).

enter image description here

Here's a JavaScript function to return the path you'll need:

function draw_curve(Ax, Ay, Bx, By, M) {

    // Find midpoint J
    var Jx = Ax + (Bx - Ax) / 2
    var Jy = Ay + (By - Ay) / 2

    // We need a and b to find theta, and we need to know the sign of each to make sure that the orientation is correct.
    var a = Bx - Ax
    var asign = (a < 0 ? -1 : 1)
    var b = By - Ay
    var bsign = (b < 0 ? -1 : 1)
    var theta = Math.atan(b / a)

    // Find the point that's perpendicular to J on side
    var costheta = asign * Math.cos(theta)
    var sintheta = asign * Math.sin(theta)

    // Find c and d
    var c = M * sintheta
    var d = M * costheta

    // Use c and d to find Kx and Ky
    var Kx = Jx - c
    var Ky = Jy + d

    return "M" + Ax + "," + Ay +
           "Q" + Kx + "," + Ky +
           " " + Bx + "," + By
}

You can see this in action at this jsfiddle or the snippet (below).

Edit: If a quadratic curve doesn't fit, you can pretty easily adapt the function to do cubic Bezier or arc segments.

var adjacencyList = {
  1: [2],
  2: [3],
  3: [1],
};

var nodes = d3.values(adjacencyList),
  links = d3.merge(nodes.map(function(source) {
    return source.map(function(target) {
      return {
        source: source,
        target: adjacencyList[target]
      };
    });
  }));

var w = 960,
  h = 500;

var M = 50;

var vis = d3.select("#svg-container").append("svg")
  .attr("width", w)
  .attr("height", h);

var force = d3.layout.force()
  .nodes(nodes)
  .links(links)
  .size([w, h])
  .linkDistance(100)
  .charge(-100)
  .start();

var link = vis.selectAll(".link")
  .data(links)
  .enter().append("svg:path")
  .attr("class", "link");

console.log(link)

var node = vis.selectAll("circle.node")
  .data(nodes)
  .enter().append("svg:circle")
  .attr("r", 5)
  .call(force.drag);

force.on("tick", function() {
  link.attr("d", function(d) {
    return draw_curve(d.source.x, d.source.y, d.target.x, d.target.y, M);
  });

  node.attr("cx", function(d) {
      return d.x;
    })
    .attr("cy", function(d) {
      return d.y;
    });
});





function draw_curve(Ax, Ay, Bx, By, M) {

  // side is either 1 or -1 depending on which side you want the curve to be on.
  // Find midpoint J
  var Jx = Ax + (Bx - Ax) / 2
  var Jy = Ay + (By - Ay) / 2

  // We need a and b to find theta, and we need to know the sign of each to make sure that the orientation is correct.
  var a = Bx - Ax
  var asign = (a < 0 ? -1 : 1)
  var b = By - Ay
  var bsign = (b < 0 ? -1 : 1)
  var theta = Math.atan(b / a)

  // Find the point that's perpendicular to J on side
  var costheta = asign * Math.cos(theta)
  var sintheta = asign * Math.sin(theta)

  // Find c and d
  var c = M * sintheta
  var d = M * costheta

  // Use c and d to find Kx and Ky
  var Kx = Jx - c
  var Ky = Jy + d

  return "M" + Ax + "," + Ay +
    "Q" + Kx + "," + Ky +
    " " + Bx + "," + By
}
.node {
  stroke: #fff;
  stroke-width: 1.5px;
}

.link {
  stroke: #ccc;
  fill: none
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.0.0/d3.min.js"></script>

<body>
  <div id="svg-container">
  </div>
</body>


Related Query