Drag along path in d3

2019-09-18 18:19发布

Using d3 and React I have drawn a path. On this path I have multiple circles which are draggable only along that path. However, my current implementation only (sort of) works when there is one circle on that path.

(On dragStart it moves to length 0 on the path regardless of position, and whenever I'm dragging a second circle it starts of the the previous circle's position).

My question is: How can I drag multiple circles (or w.e) along a path in d3? Is there a way to get the currentLength position on the path based on cx and cy of the current circle?

var currentLength = 0;

class MyComponent extends Component {

  constructor(props) {
    super(props)
    currentLength = 0;
  }

  componentDidMount() {
    var drag = d3.behavior.drag()
      .on('drag', this.move);

    var g = d3.select(this._base);
    var circle = g.selectAll('circle').data(this.props.data);
    var onEnter = circle.enter();

      onEnter.append('circle')
      .attr({
        r: 10,
        cx: (d) => d.x,
        cy: (d) => d.y
      })
      .style('fill', 'blue')
      .call(drag);
  }

  move(d) {
    currentLength += d3.event.dx + d3.event.dy

    if (currentLength < 0) {
      currentLength = 0
    }

    var pointAtCurrentLength = d3.select('#path').node().getPointAtLength(currentLength)
    this.cx.baseVal.value = pointAtCurrentLength.x;
    this.cy.baseVal.value = pointAtCurrentLength.y;
  }

  render() {
    return <g ref={(c)=>this._base=c}></g>
  }
}

Something similar to this, only draggable and multiple circles: http://bl.ocks.org/mbostock/1705868

1条回答
太酷不给撩
2楼-- · 2019-09-18 18:59

Here's a quick modification to this example, which makes the circles draggable:

<!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;
}

</style>
<body>
<script src="//d3js.org/d3.v3.min.js"></script>
<script>

var points = [[600,276],[586,393],[378,388],[589,148],[346,227],[365,108]];

var width = 960,
    height = 500;

var line = d3.svg.line()
    .interpolate("cardinal");
    
var drag = d3.behavior.drag()
    .on("drag", dragged);

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("transform", "translate(" + points[0] + ")")
  .attr("r", 7)
  .call(drag);
    
svg.append("circle")
  .attr("transform", "translate(" + points[5] + ")")
  .attr("r", 7)
  .call(drag);

function dragged(d) {
  var m = d3.mouse(svg.node()),
    p = closestPoint(path.node(), m);

  d3.select(this)
    .attr("transform", "translate(" + p[0] + "," + 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>

查看更多
登录 后发表回答