|  | <!DOCTYPE html> | 
        
          |  | <meta charset="utf-8"> | 
        
          |  | <style> | 
        
          |  |  | 
        
          |  | 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; | 
        
          |  | } | 
        
          |  |  | 
        
          |  | </style> | 
        
          |  | <body> | 
        
          |  | <script src="//d3js.org/d3.v3.min.js"></script> | 
        
          |  | <script> | 
        
          |  |  | 
        
          |  | 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(); | 
        
          |  |  | 
        
          |  | 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(points, 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 closestPointOLD(pathNode, point) { | 
        
          |  | var pathLength = pathNode.getTotalLength(), | 
        
          |  | precision = 8, | 
        
          |  | 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 /= 2; | 
        
          |  | while (precision > 0.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 /= 2; | 
        
          |  | } | 
        
          |  | } | 
        
          |  |  | 
        
          |  | 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; | 
        
          |  | } | 
        
          |  | } | 
        
          |  |  | 
        
          |  |  | 
        
          |  |  | 
        
          |  |  | 
        
          |  |  | 
        
          |  | function closestPoint(points, point) { | 
        
          |  | var pathLength = getLineDistance(points), //pathNode.getTotalLength(), | 
        
          |  | precision = 8, | 
        
          |  | best, | 
        
          |  | bestLength, | 
        
          |  | bestDistance = Infinity; | 
        
          |  |  | 
        
          |  | // linear scan for coarse approximation | 
        
          |  | for (var scan, scanLength = 0, scanDistance; scanLength <= pathLength; scanLength += precision) { | 
        
          |  | if ((scanDistance = distance2(scan = getPointAtDistance(points,scanLength))) < bestDistance) { | 
        
          |  | best = scan, bestLength = scanLength, bestDistance = scanDistance; | 
        
          |  | } | 
        
          |  | } | 
        
          |  |  | 
        
          |  | // binary search for precise estimate | 
        
          |  | precision /= 2; | 
        
          |  | while (precision > 0.5) { | 
        
          |  | var before, | 
        
          |  | after, | 
        
          |  | beforeLength, | 
        
          |  | afterLength, | 
        
          |  | beforeDistance, | 
        
          |  | afterDistance; | 
        
          |  |  | 
        
          |  |  | 
        
          |  | if ((beforeLength = bestLength - precision) >= 0 && (beforeDistance = distance2(before = getPointAtDistance(points,beforeLength))) < bestDistance) { | 
        
          |  | best = before, bestLength = beforeLength, bestDistance = beforeDistance; | 
        
          |  | } else if ((afterLength = bestLength + precision) <= pathLength && (afterDistance = distance2(after = getPointAtDistance(points,afterLength))) < bestDistance) { | 
        
          |  | best = after, bestLength = afterLength, bestDistance = afterDistance; | 
        
          |  | } else { | 
        
          |  | precision /= 2; | 
        
          |  | } | 
        
          |  | } | 
        
          |  |  | 
        
          |  | best = [best.x, best.y]; | 
        
          |  | best.distance = Math.sqrt(bestDistance); | 
        
          |  | return best; | 
        
          |  |  | 
        
          |  |  | 
        
          |  |  | 
        
          |  |  | 
        
          |  | function dist(p1,p2){ | 
        
          |  | var dx = p1[0] - p2[0], | 
        
          |  | dy = p1[1] - p2[1]; | 
        
          |  |  | 
        
          |  | return Math.sqrt((dx * dx) + (dy * dy)); | 
        
          |  | } | 
        
          |  |  | 
        
          |  |  | 
        
          |  |  | 
        
          |  | function getDistancesAlongLine(line){ | 
        
          |  |  | 
        
          |  | var tot = 0; | 
        
          |  | for(var i=0;i < line.length;i ++){ | 
        
          |  | if(i == 0){ | 
        
          |  | line[i].dist = 0; | 
        
          |  | line[i].distto = 0; | 
        
          |  | }else{ | 
        
          |  | line[i].dist = dist(line[i-1],line[i]) | 
        
          |  | line[i].distto = line[i].dist + tot; | 
        
          |  | } | 
        
          |  | tot += line[i].dist; | 
        
          |  |  | 
        
          |  | } | 
        
          |  | line.bisector = d3.bisector(function(d){ | 
        
          |  | return d.distto; | 
        
          |  | }).left; | 
        
          |  | line.dist = tot; | 
        
          |  |  | 
        
          |  | } | 
        
          |  |  | 
        
          |  | function getLineDistance(line){ | 
        
          |  | if(!line.dist){ | 
        
          |  | getDistancesAlongLine(line); | 
        
          |  | } | 
        
          |  | return line.dist; | 
        
          |  | } | 
        
          |  |  | 
        
          |  | function changeX(m,dist){ | 
        
          |  | return m == Infinity ? dist : dist * (m / Math.sqrt(1 + m*m)) | 
        
          |  | } | 
        
          |  |  | 
        
          |  | function changeY(m,dist){ | 
        
          |  | return m == Infinity ? 0 : dist * (1 / Math.sqrt(1 + m*m)) | 
        
          |  | } | 
        
          |  |  | 
        
          |  | function getPointAtDistance(_line,dist){ | 
        
          |  | if(!_line.bisector){ | 
        
          |  | getDistancesAlongLine(_line); | 
        
          |  | } | 
        
          |  |  | 
        
          |  | var i = _line.bisector(_line,dist) - 1; | 
        
          |  | if(i < 0){ | 
        
          |  | i = 0; | 
        
          |  | } | 
        
          |  | var _dist = _line[i].distto,//d3.sum(_line.slice(0,i),function(d){return d.dist}), | 
        
          |  | _remain = dist - _dist, | 
        
          |  | pt = _line[i]; | 
        
          |  |  | 
        
          |  | if(_remain == 0 || i == (_line.length-1)){ | 
        
          |  | return pt; | 
        
          |  | }else{ | 
        
          |  |  | 
        
          |  | var nxtpt = _line[i + 1]; | 
        
          |  | var x = pt[0] - (pt[0] - nxtpt[0]) * (_remain/nxtpt.dist), | 
        
          |  | y = pt[1] - (pt[1] - nxtpt[1]) * (_remain/nxtpt.dist); | 
        
          |  |  | 
        
          |  | return {x:x,y:y}; | 
        
          |  |  | 
        
          |  | } | 
        
          |  |  | 
        
          |  | } | 
        
          |  |  | 
        
          |  | function distance2(p) { | 
        
          |  | var dx = p.x - point[0], | 
        
          |  | dy = p.y - point[1]; | 
        
          |  | return dx * dx + dy * dy; | 
        
          |  | } | 
        
          |  | } | 
        
          |  |  | 
        
          |  | </script> |