沿d3中的路径拖动 [英] Drag along path in d3

查看:65
本文介绍了沿d3中的路径拖动的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

使用d3和React,我已经画了一条路.在此路径上,我有多个只能沿该路径拖动的圆.但是,当该路径上有一个圆时,我当前的实现方式(仅适用于这种情况).

(在dragStart上,无论位置如何,它都将移动到路径上的长度0,并且每当我拖动第二个圆时,它就从上一个圆的位置开始).

我的问题是:如何沿着d3中的路径拖动多个圆(或w)?有没有一种方法可以根据当前圆的cx和cy获取路径上的currentLength位置?

 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

解决方案

以下是对此示例<的快速修改,这使得圆圈可拖动:

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

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

解决方案

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>

这篇关于沿d3中的路径拖动的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆