Collision/overlap detection of circles in a d3 tra

2019-03-31 12:55发布

问题:

I'm using d3 to animate a route (path) on a map. When the route reaches a point along the route I'd like to popup some information.

Most of my code is based on the following example. http://bl.ocks.org/mbostock/1705868. I'm really just trying to determine if there is a way to detect when the transitioning circle collides or overlaps any of the stationary circles in this example.

回答1:

You can detect collision in your tween function. Define a collide function to be called from inside the tween function as follows:

function collide(node){
    var trans = d3.transform(d3.select(node).attr("transform")).translate,
      x1 = trans[0],
      x2 = trans[0] + (+d3.select(node).attr("r")),
      y1 = trans[1],
      y2 = trans[1] + (+d3.select(node).attr("r"));

  var colliding = false;
  points.each(function(d,i){
    var ntrans = d3.transform(d3.select(this).attr("transform")).translate,
      nx1 = ntrans[0],
      nx2 = ntrans[0] + (+d3.select(this).attr("r")),
      ny1 = ntrans[1],
      ny2 = ntrans[1] + (+d3.select(this).attr("r"));


      if(!(x1 > nx2 || x2 < nx1 || y1 > ny2 || y2 < ny1))
        colliding=true;
  })

  return colliding;
}

Where points are the stationary points, and node is the transitioning element. What collide does is check whether node overlaps with any of the points (as shown in collision detection example here).

Because we need the node to be passed to the tween function, we replace attrTween used in Mike's example, with tween:

circle.transition()
      .duration(10000)
      .tween("attr", translateAlong(path.node()))
      .each("end", transition);

Finally, the tween function calling our collide:

function translateAlong(path) {
  var l = path.getTotalLength();
  return function(d, i, a) {
    return function(t) {
      var p = path.getPointAtLength(t * l);

      d3.select(this).attr("transform","translate(" + p.x + "," + p.y + ")");

      if(collide(this))
        d3.select(this).style("fill", "red")
       else
        d3.select(this).style("fill", "steelblue")
    };
  };
}

See the full demo here



回答2:

The easiest way is to just check how "close" the transitioning circle is to the other points.

var pop = d3.select("body").append("div")
    .style("position","absolute")
    .style("top",0)
    .style("left",0)
    .style("display", "none")
    .style("background", "yellow")
    .style("border", "1px solid black");

// Returns an attrTween for translating along the specified path element.
function translateAlong(path) {
  var l = path.getTotalLength();
  var epsilon = 5;
  return function(d, i, a) {
    return function(t) {
      var p = path.getPointAtLength(t * l);
      points.forEach(function(d,i){
        if ((Math.abs(d[0] - p.x) < epsilon) && 
            (Math.abs(d[1] - p.y) < epsilon)){
          pop.style("left",d[0]+"px")
            .style("top",d[1]+20+"px")
            .style("display","block")
            .html(d);
          return false;
        }
      })
      return "translate(" + p.x + "," + p.y + ")";
    };
  };
}

The faster the circle moves the greater your epsilon will need to be.

Example here.