Single event at end of transition? (call vs each)

10,115 views
Skip to first unread message

Idan Gazit

unread,
Oct 21, 2012, 6:36:55 AM10/21/12
to d3...@googlegroups.com, yuv...@gmail.com
Hey All,

I've got a transition on a lot of elements. When the transition is done for all the elements, I'd like to call a function once.

each("end", myfunc) calls my function once for every element in the selection/transition — no good.

call(myfunc) calls my function only once, but it doesn't happen at the end of the transition.

Any clues on how to go about running code at the end of the transition? I've looked around the event dispatcher source and the transitions source, and I feel like I'm missing something basic here about how to grab those events.

Idan


marc fawzi

unread,
Oct 21, 2012, 10:37:27 AM10/21/12
to d3...@googlegroups.com
maybe each() passes the element index to myfunc? if so you can wait
until that's equal to the number of elements - 1...

Mike Bostock

unread,
Oct 21, 2012, 11:51:08 AM10/21/12
to d3...@googlegroups.com
There's no event at the end of a transition because elements can
transition with different delays and duration. If you want to call a
function when all your elements are done, you can use a reference
counting helper function, like this:

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

And then, rather than each("end", callback), say call(endall, callback):

d3.selectAll("g").transition().call(endall, function() {
console.log("all done"); });

Another approach, as Marc suggested, is to pick a single element as a
sentinel (e.g., i === 0 or i === n - 1). You could also do this by
listening to each("end") on a parent transition, say one created by
d3.transition followed by selectAll.

Mike

Roman Pearah

unread,
Apr 23, 2013, 2:11:24 PM4/23/13
to d3...@googlegroups.com
I tried this solution but still cannot get the behavior I'm after. What I'm trying to do is transition my y axis between an ordinal and linear version as the chart transitions. The transition works fine but I'm also trying to add tooltips to the major ticks of the ordinal axis only, and this has to be done each time the axis is recreated. The problem I'm running into is that I need to select ".y.axis .tick.major text" to add the tooltip attributes, but if I catch it mid-transition, the .tick.major elements from both axes are present simultaneously, leading the code to select both old and new elements, which then doesn't line up with my data. Even with the endall function, the old ticks haven't been removed when the callback is executed. They do get removed eventually if I let the code proceed, but I need to catch the code at that point to properly deploy a selection for adding tooltips to all the tick major elements.

Benjamin West

unread,
Apr 23, 2013, 2:24:35 PM4/23/13
to d3...@googlegroups.com
I like the sentinel approach.
Not sure d3 supports this, jquery has a nice is() function, translated might make for a line like this:
d3.select(this).filter(':last.myrow').length == 1).trigger('myendevent');

I often use this kind of inspection to detect when the element is the last or some other kind of sentinel.  Bootstrap/flight does this with @data-foo-for attributes, jquery has closest, is, et al.  Seems adding the class "last" to the last element and then triggering when last transition has finished working on something like $(this).is(".last.item") might not be too onerous?

-bewest

Roman Pearah

unread,
Apr 23, 2013, 2:34:24 PM4/23/13
to d3...@googlegroups.com
I think I figured it out. If my selection is the y axis, the "endall" ignores the child elements, which is where I was getting tripped up. I had been assuming that it would recursively apply. By simply selecting the child elements separately on my transition, I can get around this:

t.select(".y.axis").call(yAxis)
t.selectAll(".y.axis .tick.major").call(endAll, addTooltips)

Then in my callback, I invoke d3.select(this.parentNode).selectAll(".tick.major text"). All is good with this approach.

Godmar Back

unread,
May 16, 2013, 4:02:17 PM5/16/13
to d3...@googlegroups.com

Mike's endall does not work if the transition is empty().

I found it necessary to add a 

if (transition.empty())
   callback();

to make it work meaningfully.

Alexander Kasheverov

unread,
Feb 9, 2017, 1:58:18 AM2/9/17
to d3-js
Thanks for the solution! It helped a lot some time ago.

Here we added a few modifications (handling corner cases) for the solution http://stackoverflow.com/questions/10692100

Feel free to give a feedback if any!
Reply all
Reply to author
Forward
0 new messages