Skip to content
Advertisement

d3 run animations inside function in order

I am trying to run animations in order. Here is an example.

function rect1() {
  d3.select("svg")
    .append("rect")
    .attr("id", "r1")
    .attr("x", 300)
    .attr("y", 100)
    .attr("height", 0)
    .attr("width", 0)
    .transition()
    .duration(1000)
    .attr("height", 30)
    .attr("width", 50);
}
function rect2() {
  d3.select("svg")
    .append("rect")
    .attr("id", "r2")
    .attr("x", 300)
    .attr("y", 50)
    .attr("height", 0)
    .attr("width", 0)
    .transition()
    .duration(1000)
    .attr("height", 30)
    .attr("width", 50);
}
function highlightRect(id) {
  d3.select(id)
    .style("fill", "yellow")
} 

So i want to create a function like this so i can run them in the order rect1(), rect2(), highlightRect() one after another (after each animation ends).

function startAnim (f1, f2, f3, f3a) {
  f1();
  f2();
  f3(f3a);
}
startAnim(rect1, rect2, highlightRect, "#r1");

I have tried something like this but it gets messy when there are more animations.

// rect1
d3.select("svg")
    .append("rect")
    .attr("id", "r1")
    .attr("x", 300)
    .attr("y", 100)
    .attr("height", 0)
    .attr("width", 0)
    .transition()
    .duration(1000)
    .attr("height", 30)
    .attr("width", 50)
    .on("end", () => {
      // rect2
      d3.select("svg")
        .append("rect")
        .attr("id", "r2")
        .attr("x", 300)
        .attr("y", 50)
        .attr("height", 0)
        .attr("width", 0)
        .transition()
        .duration(1000)
        .attr("height", 30)
        .attr("width", 50)
        .on("end", ....);
    });;

Might be an ignorant question can this be done with promises?

Thanks

Advertisement

Answer

Promisifying these would actually be pretty easy:

First we’ll make the functions return the object we’re chaining on.

function rect1() {
  return d3.select("svg")
    .append("rect")
    .attr("id", "r1")
    .attr("x", 300)
    .attr("y", 100)
    .attr("height", 0)
    .attr("width", 0)
    .transition()
    .duration(1000)
    .attr("height", 30)
    .attr("width", 50);
}

function rect2() {
  return d3.select("svg")
    .append("rect")
    .attr("id", "r2")
    .attr("x", 300)
    .attr("y", 50)
    .attr("height", 0)
    .attr("width", 0)
    .transition()
    .duration(1000)
    .attr("height", 30)
    .attr("width", 50);
}

function highlightRect(id) {
  return d3.select(id)
    .style("fill", "yellow")
}

Then we can use a generic higher order function to promisify those for us.

// Returns a promise that resolves after the given
// animation function finishes. Can optionally take args as a second parameter
// obviously this could just take a function and allow consumers
// to use anonymous functions for parameter-binding, but this function
// relies on the return value, so this is, maybe, slightly less easy to
// break on accident.
function runAnimation(fn, ...args) {
    return new Promise(resolve => fn(...args).on("end", resolve));
}

Then chaining them is pretty easy:

runAnimation(rect1)
  .then(() => runAnimation(rect2))
  .then(() => runAnimation(highlightRect, "#r1"))

Making a helper that takes an array of functions or something would be pretty easy here too.

Untested, but I think the general idea would work out.

User contributions licensed under: CC BY-SA
5 People found this is helpful
Advertisement