Invoke a Callback at the End of a Transition

Invoke a callback at the end of a transition

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

// 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 chain many transitions in order.
  • The donut example that ships with D3 also uses this to chain together multiple transitions.
  • Here's my own demo that changes the style of elements at the start and end of the transition.

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

If type is specified, adds a listener for transition events, supporting both "start" and "end" events. The listener will be invoked for each individual element in the transition, even if the transition has a constant delay and duration. The start event can be used to trigger an instantaneous change as each element starts to transition. The end event can be used to initiate multi-stage transitions by selecting the current element, this, and deriving a new transition. Any transitions created during the end event will inherit the current transition ID, and thus will not override a newer transition that was previously scheduled.

See this forum thread on the topic for more details.

Finally, note that if you just want to remove the elements after they have faded out (after the transition has finished), you can use transition.remove().

How to call a `callback` function after all `transition` done?

I'm not sure if this is the best way to solve it, but it certainly works.

var fadeHandler = function () {    var items = d3.selectAll('.subAppGroup .subAppPath, .subAppGroup .subAppGroupDetail'),        todo = items.size();        items    .transition()    .delay(function (d, i) {        return i * 500;    })    .duration(500)    .style('opacity', 1)    .each("end", function () {        todo--;        if (todo === 0) {            // $.event.trigger('showMenu');            $("#allDone").fadeIn();        }    });};
fadeHandler();
.subAppGroup * {    float: left;    width: 50px;    height: 50px;    opacity: 0.2;    margin: 4px;}.subAppPath {    background-color: red;}.subAppGroupDetail {    background-color: blue;}#allDone {    display: none;    clear: both;    margin: 4px;}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script><script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<div class="subAppGroup"> <div class="subAppPath"></div> <div class="subAppGroupDetail"></div> <div class="subAppPath"></div> <div class="subAppGroupDetail"></div> <div class="subAppPath"></div> <div class="subAppGroupDetail"></div></div><div id="allDone">All done!</div>

How do I use d3 transition end to invoke a function after the transition has completed in d3 v6?

Note that the .on("end", ...) method takes a callback for the second argument, which is executed when the transition ends. The code you posted is not passing a callback, but already evaluating the renderValuesInBars function at the moment of declaration. Instead, you want to pass a callback that tells d3 that the evaluation should occur at a later time (in that case, after the transition)

Instead of:

.on("end", renderValuesInBars(data, metric, countryID, measurements))

You can pass a callback that evaluates the function:

on("end", ( ) => renderValuesInBars(data, metric, countryID, measurements))

That way you're passing a callback that says "at the end of the transition, evaluate renderValuesInBars"

D3: almost identical codes, different results

The fiddle that doesn't produce the error runs on v3, while the one that does runs on v5.

In d3v3, you could use transition.each("end",...) for events:

transition.each([type, ]listener)


If type is specified, adds a listener for transition events,
supporting "start", "end" and "interrupt" events. The listener will be
invoked for each individual element in the transition. (v3 docs)

In d3v4 and v5, this method was replaced with transition.on("end",...) for events:

selection.on(typenames[, listener[, options]]) <>


Adds or removes a listener to each selected element for the specified
event typenames. (current docs)

transition.each(function) can still be used to perform an action on each item being transitioned, but can't be used for event listening. Because of this change between versions you get an error that is t.call is not a function (it's a string: "end"), and the alert never triggers.

For d3v4 or d3v5, instead use:

transition 
.each(function() { ++n; })
.on("end", function() { if (!--n) callback.apply(this, arguments); });

Updated fiddle.

Callback after dialog transition has finished

This issue is being treated in v1.2.x milestone of Vuetify :
Heres the issue

You may consider recreating the modal wrapping it with the proper vuejs hooks as well.

D3v4 - on(end) called before transition.duration is over

Your main problem is, that you do not supply a proper callback function to .on("end",...). When calling it like .on("end", next(keyIndex+1)), it will not work as expected, because the statement next(keyIndex+1) will be immediately executed, whereby starting the next iteration step before the previous step has ended. In the next step the transition, which was just started, will be cancelled and replaced by the new transition. That way you won't have a chain of transitions having the specified durations, but some interrupted ones immediately followed by a last, non-interrupted one, which will transition all the way to the final values.

The solution is to wrap your call to next() in a function expression to provide a real callback which will be called when the end event fires.

function next(keyIndex) {
if(keyIndex < 3) {
d3.selectAll(".dot")
.data(dataForKey(keyIndex))
.transition().duration(3000)
.call(position)
.on("end", function() { next(keyIndex+1) }); // use a function wrapper
}
}

For a working demo have a look at the following snippet:

   var svg = d3.select("svg"),    margin = {top: 40, right: 40, bottom: 40, left: 40},    width = svg.attr("width") - margin.left - margin.right,    height = svg.attr("height") - margin.top - margin.bottom,    g = svg.append("g").attr("transform", "translate(" + margin.left + "," + margin.top + ")");
var xScale = d3.scaleLinear().domain([0, 10]).range([0, width]), yScale = d3.scaleLinear().domain([0, 10]).range([height, 0]);
var xAxis = d3.axisBottom().scale(xScale).ticks(12, d3.format(",d")), yAxis = d3.axisLeft().scale(yScale); svg.append("g") .attr("class", "x axis") .attr("transform", "translate(0," + height + ")") .call(xAxis);
svg.append("g") .attr("class", "y axis") .call(yAxis); // inital draw var dot = svg.append("g") .attr("class", "dots") .selectAll(".dot") .data(dataForKey(1)) .enter().append("circle") .attr("class", "dot") .style("fill", "#000000") .attr("cx", function(d) { return xScale(d.x); }) .attr("cy", function(d) { return yScale(d.y); }) .attr("r", function(d) { return 5; }); // updated Data next(1); function dataForKey(key) { return [{x:key, y:key}]; } function next(keyIndex) { if (keyIndex < 10) { d3.selectAll(".dot") .data(dataForKey(keyIndex)) .transition().duration(3000) .attr("cx", function(d) { return xScale(d.x); }) .attr("cy", function(d) { return yScale(d.y); }) .on("end", function() { next(keyIndex + 1) }); } }
<script src="https://d3js.org/d3.v4.js"></script>
<svg width="300" height="300"></svg>

d3 callback function after .remove()

The correct way to do it is to use .each("end", callback). As you point out, this is called once for each element in the transition. You can't change this, but you can add a counter that keeps track of how many elements have been removed:

d3.selectAll("div")
.call(setupRemove)
.transition().duration(500)
.each("end", onRemove)
.remove();

function setupRemove(sel) {
counter = sel.size();
}
function onRemove() {
counter--;
if(counter == 0) {
console.log("all done");
}
}

Complete demo here. Note that in the special case when you want to run another transition when the first set is finished, you can use just .transition() again.

d3 transition wait for previous?

There are two quick options available:

transition.delay()

You could use transition.delay(time) which allows you to specify a delay before a transition starts. This would look like:

d3.select('#' + linkId[0]).transition().duration(2500).attr('stroke', 'green');
d3.select('#' + nodeId[0]).transition().delay(2500).duration(5000).attr('fill', 'blue');

While simple, I'd suggest using the next approach instead.

transition.on("end", ... )

Another option is to use transition.on("end", function() { /* set up next transition */ }). Now .on("end",callbackFunction) will trigger on the end of each transition (if transitioning many elements, it'll trigger when each element finishes its transition), but you are transitioning single elements (as IDs are unique), so you could use something like this:

d3.select('#' + linkId[0]).transition()
.duration(2500)
.attr('stroke', 'green')
.on("end", function() {
d3.select('#' + nodeId[0]).transition().duration(5000).attr('fill', 'blue');
})

If you had many elements transitioning simultaneously you'd need to modify this slightly to check to see if any transitions were still in progress before starting the next transition.



Related Topics



Leave a reply



Submit