Callback callback at transition end

I need to make a FadeOut method (similar to jQuery) using D3.js. I need to make the opacity be 0 with transition() .

 d3.select("#myid").transition().style("opacity", "0"); 

The problem is that I need a callback to implement when the transition is complete. How to implement a callback?

+94
javascript transition
May 21 '12 at 20:25
source share
8 answers

You want to listen to the "end" of the transition event.

 // d3 v5 d3.select("#myid").transition().style("opacity","0").on("end", myCallback); // old way d3.select("#myid").transition().style("opacity","0").each("end", myCallback); 
  • This demo uses the "end" event to link many transitions in order.
  • The donut example that ships with D3 also uses this to combine multiple transitions.
  • Here is my own demo, which changes the style of the elements at the beginning and at the end of the transition.

From the documentation for transition.each([type],listener) :

If a type is specified, a listener for transition events is added that supports the "start" and "end" events. A listener will be called for each individual element in the transition, even if the transition has a constant delay and duration. The start event can be used to instantly change when each element begins a transition. The final event can be used to initiate multi-step transitions by selecting the current this element and receiving a new transition. Any transitions created during the final event will inherit the current transition identifier and thus will not override the newer transition that was previously planned.

See this thread on the forum for more details.

Finally, note that if you just want to remove elements after they disappear (after the transition is completed), you can use transition.remove() .

+129
May 21 '12 at 20:33
source share

Mike Bostock solution for v3 with a little update:

  function endall(transition, callback) { if (typeof callback !== "function") throw new Error("Wrong callback in endall"); if (transition.size() === 0) { callback() } var n = 0; transition .each(function() { ++n; }) .each("end", function() { if (!--n) callback.apply(this, arguments); }); } d3.selectAll("g").transition().call(endall, function() { console.log("all done") }); 
+65
Dec 25 '13 at 14:35
source share

Now, in d3 v4.0, there is a tool for explicitly attaching event handlers to transitions:

https://github.com/d3/d3-transition#transition_on

To execute the code when the transition is complete, all you need is:

 d3.select("#myid").transition().style("opacity", "0").on("end", myCallback); 
+44
Jul 23 '16 at 3:54 on
source share

A slightly different approach, which also works when there are many transitions with many elements, each of which is performed simultaneously:

 var transitions = 0; d3.select("#myid").transition().style("opacity","0").each( "start", function() { transitions++; }).each( "end", function() { if( --transitions === 0 ) { callbackWhenAllIsDone(); } }); 
+10
Jul 24 '14 at 19:23
source share

Below is another version of the Mike Bostock solution and is inspired by @hughes comment on @kashesandr's answer. It makes one callback at the end of the transition .

For the 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 expand d3 like this:

 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 routine var n = 0; transition.each(function() { ++n; }) .each("end", function() { if (!--n) f.apply(transition, args); }); } }); return transition; } 

Like JSFiddle .

Use transition.end(callback[, delayIfEmpty[, arguments...]]) :

 transition.end(function() { console.log("all done"); }); 

... or with extra delay if transition 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 true. It also redirects any additional arguments to callback (and only those arguments). It is important to note that it does not use callback by default if transition empty, which is probably a safer assumption in this case.

+6
May 28 '15 at 13:03
source share

Mike Bostock solution improved by kashesandr + passing arguments to the callback function:

 function d3_transition_endall(transition, callback, arguments) { if (!callback) callback = function(){}; if (transition.size() === 0) { callback(arguments); } var n = 0; transition .each(function() { ++n; }) .each("end", function() { if (!--n) callback.apply(this, arguments); }); } function callback_function(arguments) { console.log("all done"); console.log(arguments); } d3.selectAll("g").transition() .call(d3_transition_endall, callback_function, "some arguments"); 
0
Jan 24 '17 at 18:08
source share

There is actually another way to do this with timers.

 var timer = null, timerFunc = function () { doSomethingAfterTransitionEnds(); }; transition .each("end", function() { clearTimeout(timer); timer = setTimeout(timerFunc, 100); }); 
-2
May 21 '14 at 13:10
source share

I solved a similar problem by setting the duration of transitions using a variable. Then I used setTimeout() to call the next function. In my case, I wanted to overlap a bit between the transition and the next call, as you will see in my example:

 var transitionDuration = 400; selectedItems.transition().duration(transitionDuration).style("opacity", .5); setTimeout(function () { sortControl.forceSort(); }, (transitionDuration * 0.75)); 
-2
Feb 02 '15 at 18:22
source share



All Articles