Chaining transitions using the transition.end() promise

Viewed 395

I'm trying to chain transitions by calling my update function when the transition.end() promise resolves. The following snippet lies within this update function. The full example, of which this snippet is a part, can be found at https://jsfiddle.net/fmgreenberg/1npLeguh/10/.

let t = d3
    .transition()
    .duration(3000)
    .end()
    .then(() => update(newData));

The issue is that the transition happens almost instantaneously, then the visualization sits there for ~3 seconds until update is called again. Why is this? If I comment out the last two lines of the snippet, the transition takes the expected 3 seconds. (Of course, there's just a single transition in this case since I've removed the loop.)

1 Answers

Instead of naming a transition instance, use the promise in the transition selection itself:

d3.select("#figure")
    .selectAll("circle")
    .data(newData, d => d)
    .transition()
    .duration(3000)
    .attr("cx", (_, i) =>
      i < N ? (i + 1) * (2 * r + s) : 300 - (i - N + 1) * (2 * r + s)
    )
    .end()
    .then(() => update(newData));

Here is the updated JSFiddle: https://jsfiddle.net/k9gf8ybL/

And the corresponding S.O. snippet:

let N = 5;
let r = 5;
let s = 2;

let data = d3.range(2 * N);

d3.select("#figure")
  .selectAll("circle")
  .data(data)
  .enter()
  .append("circle")
  .attr("cx", (_, i) =>
    i < N ? (i + 1) * (2 * r + s) : 300 - (i - N + 1) * (2 * r + s)
  )
  .attr("cy", 26)
  .attr("r", r)
  .attr("stroke", "blue")
  .attr("fill", d => (d < N ? "white" : "black"));

update(data);

function update(data) {
  let I = data.slice(0, N);
  let J = data.slice(N, 2 * N);
  let i = randInt(N);
  let x = I[i];
  let j = randInt(N);
  let y = J[j];
  let newData = [
    ...I.slice(0, i),
    ...I.slice(i + 1),
    y,
    ...J.slice(0, j),
    ...J.slice(j + 1),
    x
  ];

  d3.select("#figure")
    .selectAll("circle")
    .data(newData, d => d)
    .transition()
    .duration(3000)
    .attr("cx", (_, i) =>
      i < N ? (i + 1) * (2 * r + s) : 300 - (i - N + 1) * (2 * r + s)
    )
    .end()
    .then(() => update(newData));
}

function randInt(n) {
  return Math.floor(Math.random() * n);
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.9.0/d3.min.js"></script>
<svg id="figure" viewBox="0 0 300 50" style="background-color: papayawhip; border-radius: 1rem" xmlns="http://www.w3.org/2000/svg"></svg>

However, if for any reason you still want to use a named transition instance, just use the more common on("end"...) method instead of the end() promise:

let t = d3
    .transition()
    .duration(3000)
    .on("end", () => {
      update(newData)
    });

And then:

selection.transition(t)
    //etc...

Here is the JSFiddle with that approach: https://jsfiddle.net/8od3vkc1/

Related