Перетащите по пути в d3

Используя d3 и React, я нарисовал путь. На этом пути у меня есть несколько кругов, которые можно перетаскивать только по этому пути. Однако моя текущая реализация (вроде как) работает только тогда, когда на этом пути есть один круг.

(При dragStart он перемещается на длину 0 по пути независимо от положения, и всякий раз, когда я перетаскиваю второй круг, он начинается с позиции предыдущего круга).

Мой вопрос: как я могу перетащить несколько кругов (или мы) по пути в d3? Есть ли способ получить позицию currentLength на пути на основе cx и cy текущего круга?

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

Что-то похожее на это, только с возможностью перетаскивания и несколькими кругами: http://bl.ocks.org/mbostock/1705868< /а>


person mrlarssen    schedule 07.07.2016    source источник
comment
Можете ли вы создать скрипт JS с этим фрагментом? Трудно отлаживать этот код, не видя его в действии. Одна проблема, которую я вижу, заключается в том, что вы не привязываете move к своему классу, поэтому назначение this.cy/this.cx, скорее всего, не сработает.   -  person Michael Camden    schedule 07.07.2016


Ответы (1)


Вот небольшая модификация этого примера, делающая круги перетаскиваемыми:

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

person Mark    schedule 10.07.2016
comment
Вы спасатель жизни, сэр. Благодарю вас! - person mrlarssen; 11.07.2016