The following is another version of Mike Bostock's solution and inspired by @hughes' comment to @kashesandr's answer. It makes a single callback upon transition
's end.
Given a drop
function...
function drop(n, args, callback) {
for (var i = 0; i < args.length - n; ++i) args[i] = args[i + n];
args.length = args.length - n;
callback.apply(this, args);
}
... we can extend d3
like so:
d3.transition.prototype.end = function(callback, delayIfEmpty) {
var f = callback,
delay = delayIfEmpty,
transition = this;
drop(2, arguments, function() {
var args = arguments;
if (!transition.size() && (delay || delay === 0)) { // if empty
d3.timer(function() {
f.apply(transition, args);
return true;
}, typeof(delay) === "number" ? delay : 0);
} else { // else Mike Bostock's routine
var n = 0;
transition.each(function() { ++n; })
.each("end", function() {
if (!--n) f.apply(transition, args);
});
}
});
return transition;
}
As a JSFiddle.
Use transition.end(callback[, delayIfEmpty[, arguments...]])
:
transition.end(function() {
console.log("all done");
});
... or with an optional delay if transition
is empty:
transition.end(function() {
console.log("all done");
}, 1000);
... or with optional callback
arguments:
transition.end(function(x) {
console.log("all done " + x);
}, 1000, "with callback arguments");
d3.transition.end
will apply the passed callback
even with an empty transition
if the number of milliseconds is specified or if the second argument is truthy. This will also forward any additional arguments to the callback
(and only those arguments). Importantly, this will not by default apply the callback
if transition
is empty, which is probably a safer assumption in such a case.