Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

d3.js: distance point to svg:path

Tags:

d3.js

Is there an (efficient) method to (a) calculate the shortest distance between a fixed point and a svg:path element in d3.js and (b) determine the point on the path which belongs to this distance?

like image 427
Michael Avatar asked Feb 12 '13 15:02

Michael


2 Answers

In the general case, I don´t think so. An SVG path is a complex element. For instance, if the path is a Bezier curve, the control points may be off the represented line, and the represented shape may be off the bounding box of the control points.

I think that if you have a set of points that you use to generate the path, you may use this points to compute the distance from this points to a given point and get the minimum distance. In the MDN SVG Path Tutorial you can find some examples of complex shapes and how they are made.

like image 167
Pablo Navarro Avatar answered Nov 18 '22 12:11

Pablo Navarro


Although my calculus answer is still valid, you could just do everything in this bl.ocks example:

var points = [[474,276],[586,393],[378,388],[338,323],[341,138],[547,252],[589,148],[346,227],[365,108],[562,62]];

var width = 960,
    height = 500;

var line = d3.svg.line()
    .interpolate("cardinal");

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

var path = svg.append("path")
    .datum(points)
    .attr("d", line);

var line = svg.append("line");

var circle = svg.append("circle")
    .attr("cx", -10)
    .attr("cy", -10)
    .attr("r", 3.5);

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

function mousemoved() {
  var m = d3.mouse(this),
      p = closestPoint(path.node(), m);
  line.attr("x1", p[0]).attr("y1", p[1]).attr("x2", m[0]).attr("y2", m[1]);
  circle.attr("cx", p[0]).attr("cy", p[1]);
}

function closestPoint(pathNode, point) {
  var pathLength = pathNode.getTotalLength(),
      precision = pathLength / pathNode.pathSegList.numberOfItems * .125,
      best,
      bestLength,
      bestDistance = Infinity;

  // linear scan for coarse approximation
  for (var scan, scanLength = 0, scanDistance; scanLength <= pathLength; scanLength += precision) {
    if ((scanDistance = distance2(scan = pathNode.getPointAtLength(scanLength))) < bestDistance) {
      best = scan, bestLength = scanLength, bestDistance = scanDistance;
    }
  }

  // binary search for precise estimate
  precision *= .5;
  while (precision > .5) {
    var before,
        after,
        beforeLength,
        afterLength,
        beforeDistance,
        afterDistance;
    if ((beforeLength = bestLength - precision) >= 0 && (beforeDistance = distance2(before = pathNode.getPointAtLength(beforeLength))) < bestDistance) {
      best = before, bestLength = beforeLength, bestDistance = beforeDistance;
    } else if ((afterLength = bestLength + precision) <= pathLength && (afterDistance = distance2(after = pathNode.getPointAtLength(afterLength))) < bestDistance) {
      best = after, bestLength = afterLength, bestDistance = afterDistance;
    } else {
      precision *= .5;
    }
  }

  best = [best.x, best.y];
  best.distance = Math.sqrt(bestDistance);
  return best;

  function distance2(p) {
    var dx = p.x - point[0],
        dy = p.y - point[1];
    return dx * dx + dy * dy;
  }
}
path {
  fill: none;
  stroke: #000;
  stroke-width: 1.5px;
}

line {
  fill: none;
  stroke: red;
  stroke-width: 1.5px;
}

circle {
  fill: red;
}

rect {
  fill: none;
  cursor: crosshair;
  pointer-events: all;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>

And I spent all that time in the previous answer writing up pretty LaTeX!

like image 2
spamguy Avatar answered Nov 18 '22 10:11

spamguy