Independently Moving Dots

This is a really interesting problem that has lots of interesting applications in visualization.


data = [
  { step: 1, x: 1, y: 1, guy: "A", speed: 0 },
  { step: 2, x: 2, y: 2, guy: "A", speed: 1000 },
  { step: 3, x: 4, y: 1, guy: "A", speed: 2000 },
  { step: 4, x: 7, y: 2, guy: "A", speed: 3000 },
  { step: 1, x: 1, y: 4, guy: "B", speed: 0 },
  { step: 2, x: 2, y: 3, guy: "B", speed: 4000 },
  { step: 3, x: 3, y: 3, guy: "B", speed: 5000 },
  { step: 4, x: 5, y: 4, guy: "B", speed: 4000 },
];

var g = d3.select("#figurecontainer")
    .append("svg")
    .attr("height", height)
    .attr("width", width)

var line = d3.line()
  .x(function (d) { return x(d.x); })
  .y(function (d) { return y(d.y); })
  .curve(d3.curveCardinal.tension(0));

data.forEach(function (d) {
  d.guy = d.guy;
  d.step = +d.step;
  d.x = +d.x;
  d.y = +d.y;
  d.speed = +d.speed;
});

var pathData = d3.nest()
  .key(function (d) { return d.guy; })
  .entries(data);

x.domain([d3.min(data, function (d) { return d.x }) - 1,
d3.max(data, function (d) { return d.x }) + 1]);
y.domain([d3.min(data, function (d) { return d.y }) - 1,
d3.max(data, function (d) { return d.y }) + 1]);

g.selectAll(".dot")
  .data(data)
  .enter().append("circle")
  .attr("cx", function (d) { return x(d.x); })
  .attr("cy", function (d) { return y(d.y + 0.5); })
  .transition()
  .ease(d3.easeElastic)
  .duration(2000)
  .attr("cy", function (d) { return y(d.y); })
  .attr("r", 6)
  .style("fill", "pink")

//////////////////////////////////////////////////////////////////
var totalLengths = [];
pathData.forEach(function (d) {
  eachGuy = d.values;

  circle = g.append("circle")
    .attr("transform", `translate(${x(eachGuy[0].x)}, ${y(eachGuy[0].y)})`)
    .attr("r", 20)
    .style("fill", 'blue')
    .style("opacity", 0.1)


  ///////////// Add paths and segments 
  mypaths = g.append("path")
    .style("stroke", "black")
    .style("fill", "none")
    .style("opacity", 0.3)
    .attr("d", line(eachGuy))
    .attr("class", "temppath")

  totalLengths.push(mypaths.node().getTotalLength())

  // For each segment of each line, need to find segment lengths
  var mySegments = [];
  for (j = 0; j < 3; j++) {
    segments = g
      .append("path")
      .attr("d", function (d) {
        return line(eachGuy.slice(j, (j + 2)))
      })

    mySegments.push(segments);

    segments.each(function () {
      eachGuy[0].seglength = 0;
      eachGuy[j + 1].seglength = this.getTotalLength()
    });
  }

  ///////////// This is the animation portion
  var transitionFrom = circle.transition()
    .duration(eachGuy[1].speed)
    .attrTween("transform", getPath(mySegments[0].node()));

  for (i = 1; i < 3; i++) {
    transitionFrom = transitionFrom.transition()
      .duration(eachGuy[i + 1].speed)
      .attrTween("transform", getPath(mySegments[i].node()))
  }

  function getPath(path) {
    var l = path.getTotalLength();
    return function () {
      return function (t) {
        var p = path.getPointAtLength(t * l);
        return "translate(" + p.x + "," + p.y + ")";
      };
    };
  };
});