3 changed files with 148 additions and 9 deletions
@ -0,0 +1,118 @@ |
|||
<!DOCTYPE html> |
|||
<html> |
|||
<!-- source: https://bl.ocks.org/mbostock/8027637 --> |
|||
<head> |
|||
<meta charset="utf-8"> |
|||
<style> |
|||
|
|||
path { |
|||
fill: none; |
|||
stroke: #000; |
|||
stroke-width: 8px; |
|||
} |
|||
|
|||
line { |
|||
fill: none; |
|||
stroke: red; |
|||
stroke-width: 0; |
|||
} |
|||
|
|||
circle { |
|||
fill: red; |
|||
} |
|||
|
|||
rect { |
|||
fill: none; |
|||
cursor: crosshair; |
|||
pointer-events: all; |
|||
} |
|||
|
|||
</style> |
|||
<body> |
|||
<script src="https://d3js.org/d3.v3.min.js"></script> |
|||
<script> |
|||
|
|||
var points = [[600,600],[600, 400],[300,300], [400, 600]]; |
|||
|
|||
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", 60); |
|||
|
|||
var isDown = false; |
|||
svg.append("rect") |
|||
.attr("width", width) |
|||
.attr("height", height) |
|||
.on("mousedown", function(){ |
|||
svg.on("mousemove", mousemoved); |
|||
}) |
|||
.on("mouseup") |
|||
|
|||
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 = 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; |
|||
} |
|||
} |
|||
|
|||
</script> |
|||
<html> |
Loading…
Reference in new issue