D3 transition along segments of path and pause at

2019-08-13 14:45发布

I would like to be able to click on a circle (coordinate points); bring the marker to the position of the circle and pause at the position of the circle and then resume again along the path.

In addition I would like to activate a circle when marker is paused on them - they are clicked (or their Voronoi cell is clicked). My intention is to have an on click function to an href for the circle coordinates eventually.

I think I need to pass the index of the path coordinates into the translateAlong function instead of the time variables but can't work out how to do this.

I’m not sure if the Voronoi cells are necessary - I tried to add this thinking I could pause my transition and activate my circles with the Voronoi cells. In any case I can’t activate the circle with the Voronoi cell.

I was helped considerably recently on Stackoverflow d3 on click on circle pause and resume transition of marker along line and I am hoping for assistance again

<!DOCTYPE html>
<html lang="en">
    <head>
<meta charset="utf-8">
<title>basic_animateBetweenCircles</title>

<script src="https://d3js.org/d3.v4.min.js"></script>
<style>
path {
  stroke: #848484;
    fill: none;
}
circle {
    fill: steelblue;
    stroke: steelblue;
    stroke-width: 3px;
}

.line {
    fill: none;
    stroke: #FE642E;
    stroke-width: 4;
    stroke-dasharray: 4px, 8px;
}
.point{
    fill:#DF013A;
}
</style>
</head>
<body>

<script>

var width = 960,
    height = 500;

var data = [
        [480, 200],
        [580, 400],
        [680, 100],
        [780, 300],
        [180, 300],
        [280, 100],
        [380, 400]
    ];

    //check index of path data
      for (var i = 0; i < data.length; i++) {
            var coordindex = i + " " + data[i];
            console.log("Coordindex: " + coordindex);
            //return coordindex;
      };

var duration = 20000;

var line = d3.line()
    .x(function(d) {return (d)[0];})
    .y(function(d) {return (d)[1];});

var voronoi = d3.voronoi()
  .extent([[0, 0], [width, height]]);

var svg = d3.select("body")
    .append("svg")
    .attr("width", width)
    .attr("height", height);

//path to animate - marker transitions along this path
var path = svg.append("path")
    .data([data])
    .attr("d", line)
    .attr('class', 'line')
    .attr("d", function(d) {
        return line(d)
    });

//voronoi
var voronoiPath = svg.append("g")
    .selectAll("path")
    .data(voronoi.polygons(data))
    .enter().append("path")
    .attr("d", polygon)
    .on("touchmove mousemove", function() {
        d3.select(this)
         .style("fill", "purple");
 });

//Want to activate circles when marker paused on them / in voronoi cell - intention is to have on click to href
 svg.selectAll("circle")
        .data(data)
    .enter()
        .append("circle")
        .attr("class", "point")
        .attr("r", 10)
    .attr("transform", function(d) { return "translate(" + d + ")"; })
        .on('click', function(d, i) {
        d3.select(this)
            .style("fill", "green");
        if (d3.active(this)) {
            marker.transition();
            setTimeout(function() {
                pauseValues.lastTime = pauseValues.currentTime;
                //console.log(pauseValues);
            }, 100);
        } else {
            transition();
        }
    });

var pauseValues = {
    lastTime: 0,
    currentTime: 0
};

//marker to transition along path
var marker = svg.append("circle")
    .attr("r", 19)
    .attr("transform", "translate(" + (data[0]) + ")")
    .on('click', function(d, i) {
        if (d3.active(this)) {
            marker.transition();
            setTimeout(function() {
                pauseValues.lastTime = pauseValues.currentTime;
                //console.log(pauseValues);
            }, 100);
        } else {
            transition();
        }
    });

function transition() {
    marker.transition()
        .duration(duration - (duration * pauseValues.lastTime))
        .attrTween("transform", translateAlong(path.node()))
        .on("end", function() {
            pauseValues = {
                lastTime: 0,
                currentTime: 0
            };
            transition()
        });
}

function translateAlong(path) {
    var l = path.getTotalLength();
    return function(d, i, a) {
        return function(t) {
            t += pauseValues.lastTime;
            var p = path.getPointAtLength(t * l);
            pauseValues.currentTime = t;
            return "translate(" + p.x + "," + p.y + ")";
        };
    };
}

function polygon(d) {
  return "M" + d.join("L") + "Z";
}

</script>
</body>

1条回答
Explosion°爆炸
2楼-- · 2019-08-13 14:52

If you want to pause at points, I would not run one transition across the entire path. Instead, I would break it up into N transitions, moving from point to point. Before starting the circle on it's next leg, you can pause it for a time. To do this, I would just transition along each line segment with a little algebra:

// copy our data
transData = data.slice();

function transition() {
  marker.transition()
    .ease(d3.easeLinear)
    .duration(duration)
    .attrTween("transform", function(){

      // get our two points
      // slope between them
      // and intercetp
      var p0 = transData.shift(),
          p1 = transData[0];
          m = (p0[1] - p1[1]) / (p0[0] - p1[0]),
          b = p0[1] - (m * p0[0]),
          i = d3.interpolateNumber(p0[0], p1[0]);

        // move the point along the line
        return function(t){
          var x = i(t),
              y = m*x + b;
          return "translate(" + x + "," + y + ")";
        }
    })
    // one line segment is complete
    .on("end", function(){
      // if no more movements, stop
      if (transData.length <= 1) return;
      iter++;  
      // determine if this is a "pause"        
      setTimeout(transition, pausePoints.indexOf(iter) !== -1 ? pauseTime : 0);
    });

Running code, click a dot to start you can pause a multiple points:

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="utf-8">
  <title>basic_animateBetweenCircles</title>

  <script src="https://d3js.org/d3.v4.min.js"></script>
  <style>
    path {
      stroke: #848484;
      fill: none;
    }
    
    circle {
      fill: steelblue;
      stroke: steelblue;
      stroke-width: 3px;
    }
    
    .line {
      fill: none;
      stroke: #FE642E;
      stroke-width: 4;
      stroke-dasharray: 4px, 8px;
    }
    
    .point {
      fill: #DF013A;
    }
  </style>
</head>

<body>

  <script>
    var width = 960,
      height = 500;

    var data = [
      [480, 200],
      [580, 400],
      [680, 100],
      [780, 300],
      [180, 300],
      [280, 100],
      [380, 400]
    ];


    var duration = 20000/data.length,
        pauseTime = 2000;

    var line = d3.line()
      .x(function(d) {
        return (d)[0];
      })
      .y(function(d) {
        return (d)[1];
      });

    var voronoi = d3.voronoi()
      .extent([
        [0, 0],
        [width, height]
      ]);

    var svg = d3.select("body")
      .append("svg")
      .attr("width", width)
      .attr("height", height);

    //path to animate - marker transitions along this path
    var path = svg.append("path")
      .data([data])
      .attr("d", line)
      .attr('class', 'line')
      .attr("d", function(d) {
        return line(d)
      });

    //voronoi
    var voronoiPath = svg.append("g")
      .selectAll("path")
      .data(voronoi.polygons(data))
      .enter().append("path")
      .attr("d", polygon);

    //Want to activate circles when marker paused on them / in voronoi cell - intention is to have on click to href
    svg.selectAll("circle")
      .data(data)
      .enter()
      .append("circle")
      .attr("class", "point")
      .attr("r", 10)
      .attr("transform", function(d) {
        return "translate(" + d + ")";
      })
      .on('click', function(d, i) {
        d3.select(this)
          .style("fill", "green");
        pausePoints.push(i);
        if (pausePoints.length === 1)
          transition();    
      });

    //marker to transition along path
    var marker = svg.append("circle")
      .attr("r", 19)
      .attr("transform", "translate(" + (data[0]) + ")");

    var pausePoints = [],
        iter = 0,
        transData = data.slice();
    
    function transition() {
      marker.transition()
        .ease(d3.easeLinear)
        .duration(duration)
        .attrTween("transform", function(){
          var p0 = transData.shift(),
              p1 = transData[0];
              m = (p0[1] - p1[1]) / (p0[0] - p1[0]),
              b = p0[1] - (m * p0[0]),
              i = d3.interpolateNumber(p0[0], p1[0]);
              
            return function(t){
              var x = i(t),
                  y = m*x + b;
              return "translate(" + x + "," + y + ")";
            }
        })
        .on("end", function(){
          if (transData.length <= 1) return;
          iter++;          
          setTimeout(transition, pausePoints.indexOf(iter) !== -1 ? pauseTime : 0);
        });
    }

    function polygon(d) {
      return "M" + d.join("L") + "Z";
    }
  </script>
</body>

查看更多
登录 后发表回答