D3. JS разделить путь мышью на сегменты - PullRequest
0 голосов
/ 04 марта 2020

В начале у меня есть прямая линия, описываемая двумя точками 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>
...