В начале у меня есть прямая линия, описываемая двумя точками p0 {x: 32, y: 256} и p1 {x: 476, y: 256} в массиве сегментов:
var segments = [{start: p0, end: p1}]
Прикрепленный код генерирует путь из этого массива, и он может взаимодействовать с мышью.
Мне нужна правильная функция splitPath таким образом, чтобы мышь разбивала линию на два сегмента и Обновление массива. Допустим, щелчок мыши был в точке p2 {x: 300, y: 256}, поэтому обновленный массив должен выглядеть следующим образом:
segments = [{start: p0, end: p2}, {start: p2, end: p1}]
И, в конце концов, вы должны иметь возможность разделить эти новые сегменты снова и снова.
Для одного сегмента решение довольно простое, нужно настроить closetsPoint, чтобы вернуть t от 0,0 до 1,0, а затем перенести начало и конец.
Но, я запутался, если путь будет построен из двух или более сегментов.
var svg = d3.select("#container").on("mousemove", mousemoved);
var segments = [
{ start: {x: 32, y: 256}, end: {x: 476, y: 256 } }
];
var d = generatePath();
var path = svg.append("path")
.attr("d", d)
.attr("id", "line")
.attr("stroke", "#000000")
.attr("stroke-width", 3)
.on("mouseover", function() { d3.select(this).attr("stroke", "#FF0000"); })
.on("mouseout", function() { d3.select(this).attr("stroke", "#000000"); })
.on("click", splitPath);
var nodes = getAllNodes();
var node = svg.selectAll(".node")
.data(nodes)
.enter()
.append("circle")
.attr("class", "node")
.attr("cx", function(d_) { return d_.x; })
.attr("cy", function(d_) { return d_.y; })
.attr("r", 6)
.attr("fill", "#FF00FF");
var circ = svg.append("circle").attr("r", 6).attr("fill", "#888888");
function generatePath(){
out = "";
for(var i = 0; i < segments.length; i++){
var prefix;
i == 0 ? prefix = "M" : " L";
var out = prefix + segments[i].start.x + " " + segments[i].start.y + " L" + segments[i].end.x + " " + segments[i].end.y
}
return out;
}
function splitPath(){
console.log("splits");
//code here
d = generatePath();
path.attr("d", d);
d3.selectAll(".node").remove();
nodes = getAllNodes();
var node = svg.selectAll(".node")
.data(nodes)
.enter()
.append("circle")
.attr("class", "node")
.attr("cx", function(d_) { return d_.x; })
.attr("cy", function(d_) { return d_.y; })
.attr("r", 6)
.attr("fill", "#FF00FF");
}
function getAllNodes(){
var out = [];
segments.forEach(function(segment_){
out.push(segment_.start, segment_.end);
});
return out;
}
function closestPoint(pathNode, point) {
var dist2D = function(p) { var dx = p.x - point[0], dy = p.y - point[1]; return dx * dx + dy * dy; }
var pathLength = pathNode.getTotalLength(),
precision = 8,
best,
bestLength,
bestDistance = Infinity;
for (var scan, scanLength = 0, scanDistance; scanLength <= pathLength; scanLength += precision) {
if ((scanDistance = dist2D(scan = pathNode.getPointAtLength(scanLength))) < bestDistance) {
best = scan, bestLength = scanLength, bestDistance = scanDistance;
}
}
precision /= 2;
while (precision > 0.5) {
var before,
after,
beforeLength,
afterLength,
beforeDistance,
afterDistance;
if ((beforeLength = bestLength - precision) >= 0 && (beforeDistance = dist2D(before = pathNode.getPointAtLength(beforeLength))) < bestDistance) {
best = before, bestLength = beforeLength, bestDistance = beforeDistance;
} else if ((afterLength = bestLength + precision) <= pathLength && (afterDistance = dist2D(after = pathNode.getPointAtLength(afterLength))) < bestDistance) {
best = after, bestLength = afterLength, bestDistance = afterDistance;
} else {
precision /= 2;
}
}
return best;
}
function mousemoved() {
var m = d3.mouse(this),
p = closestPoint(path.node(), m);
//console.log(p)
circ.attr("cx", p.x).attr("cy", p.y);
}
body { margin: 0; }
<!DOCTYPE html>
<meta charset="utf-8">
<body>
<script src="//d3js.org/d3.v4.min.js"></script>
<svg id="container" width="512" height="512"></svg>