16

I am applying a transition to a group of nodes returned by selectAll(). I thought the end event would fire after all transitions finished, but each("end",function) gets called at the end of each transition.

So is there any way to set a callback that will be called after transitions on all selected node finishes ?

Should I use call for this? but I don't see it used as end callback anywhere in documentation.

also I can run a counter inside each callback. but is there any way to know how many nodes are still pending to finish transition ? or index of current node in group of selected nodes ?

I've two select() calls in chain like selectAll('.partition').selectAll('.subpartition') so index argument passed to each callback will rotated n times.

seliopou
  • 2,896
  • 20
  • 19
Dipro Sen
  • 4,350
  • 13
  • 37
  • 50

3 Answers3

15

As far as I know there is not a built in way to know when the last transition of a group has finished but there are ways around it. One way that I have used several times involves maintaining a count of transitions that have finished.

var n = 0;

d3.selectAll('div')
   .each(function() { // I believe you could do this with .on('start', cb) but I haven't tested it
       n++;
   })
   .transition()
   .on('end', function() { // use to be .each('end', function(){})
       n--;
       if (!n) {
           endall();
       }
   })

function endall() {
    // your end function here
}

Here are the links to the relevant documentation:

payne8
  • 821
  • 9
  • 12
  • 5
    in 2016 in version 4 this code will not work at all! Replace each("end" with on("end" – kurumkan Oct 10 '16 at 09:51
  • 3
    @ArslArsl Thanks for the update. I have added links to the right documentation and updated the code to the 2016 version. – payne8 Oct 11 '16 at 05:48
4

Here's a clean way to accomplish what you want:

function endAll (transition, callback) {
    var n;

    if (transition.empty()) {
        callback();
    }
    else {
        n = transition.size();
        transition.each("end", function () {
            n--;
            if (n === 0) {
                callback();
            }
        });
    }
}

You can then easily call this function like so:

selection.transition()
    .call(endAll, function () {
        console.log("All the transitions have ended!");
    });

This will work even if the transition is empty.

Ashitaka
  • 19,028
  • 6
  • 54
  • 69
  • 1
    Note that you can't bind two functions on 'end' event: `transition.each("end", f1).each("end", f2)` will call only `f2`. So you shouldn't use `endAll` if using `.each("end", ...)` on the same transition. – Zelta Aug 06 '15 at 20:52
1

I had the same issue

that the call back gets executed with each element

I have solved that using underscore once method

http://underscorejs.org/#once

d3.select("#myid")
.transition()
.style("opacity","0")
.each("end", _.once(myCallback) );
  • 2
    They were asking about selectAll. select only returns one element – aharris88 Oct 21 '14 at 21:43
  • 2
    This answer does not answer the question. It will only run once but it does not necessarily run when all transitions are finished. This is an important distinction because it is possible to have transitions within the same selection last different lengths. e.g. `duration(function(d,i) {return 100 * i;})` – payne8 Jan 23 '15 at 23:36