The abstractions of D3 still make my mind bend, so hopefully I'm presenting this correctly.
In D3 version 3, given an element (say a circle), and given only one transition possibly running per element what is the best way to determine what the current running transition on that element is, if there is one at all?
I'm aware that I can manually inspect __transition__
on the element (though help there is welcome too), but I'm really hoping for something a little higher-level.
My larger goal here is to create a subtransition if-and-only-if there's a transition to sub. Otherwise, I'll be creating a new transition.
d3-transition. A transition is a selection-like interface for animating changes to the DOM. Instead of applying changes instantaneously, transitions smoothly interpolate the DOM from its current state to the desired target state over a given duration. To apply a transition, select elements, call selection.
attrTween('d',...) takes a single function that returns another function. You pass it a function that gets called with the current datum, index and current node as the parameters. This function should return the interpolation function which gets called with a time value.
Another way to do it: Create your own property on each node that stores an array of the actual d3.transition
objects. When creating a new transition, grab the last transition from the array and create a sub-transition.
The complication is that your new transition may not be based on the same selection as the active transition. Therefore, I create the new "safe" transitions on a per-element basis within an .each()
call.
function saveTransition(t) {
//save the transition immediately (don't wait for "start")
//clear it on "end"
t.each(function() {
var tArr = this.__tObj__
if (!tArr) tArr = this.__tObj__ = [];
tArr.push(t);
//console.log("saving ", t, " for ",this);
} )
.each("end", function() {
var test = this.__tObj__.shift();
// console.log("clearing ", t, " from " ,this,
// (test == t ? "correctly" : "ERROR") );
} );
}
function newSafeTransition(node) {
var tArr = node.__tObj__;
if ( tArr && tArr.length ) {
var t = tArr[ tArr.length - 1 ];
return t.filter( function(){ return this === node; } )
.transition().call(saveTransition);
}
else {
return d3.select(node).transition().call(saveTransition);
}
}
d3.selectAll("div.foo")
.transition().duration(3000)
.call( saveTransition )
.style("left", "100px");
d3.selectAll("div.bar")
.transition().duration(3000)
.call( saveTransition )
.style("top", "100px");
setTimeout( function() {
console.log("blue");
d3.selectAll("div.blue")
.each( function() {
newSafeTransition(this).style("color", "blue");
});
}, 1000);
setTimeout( function() {
console.log("reposition");
d3.selectAll("div.foo")
.each( function() {
newSafeTransition(this).style("left", "0px");
});
}, 2000);
http://jsfiddle.net/7SQBe/3/
It could probably be cleaned up, you could even over-write the selection.transition()
and transition.transition()
methods to do this automatically. However, you'd probably want to keep a way to indicate whether you want to queue the new transition after any scheduled transitions or whether you want to interrupt.
The short answer is that there's no standard way to get the transition and you're not meant to do that. As in, it's not supported.
The slightly longer answer is that for your purposes, you can probably hack it using __transition__
. The idea is to check whether __transition__
exists and if so, wait until it doesn't before starting the new (sub)transition.
To do that, it helps to extend the selection prototype with an appropriate function:
d3.selection.prototype.getTransition = function() {
if(this[0][0].__transition__) {
return this[0][0].__transition__[1];
} else return undefined;
}
Note that this here is extremely hacky and will only work if there is exactly one element in the selection with exactly one transition. You should get the idea though.
Now, we can use this function to determine whether a transition is running.
if(sel.getTransition() !== undefined) {
// transition is there
} else {
// no transition
}
Unfortunately, __transition__
does not allow you to reconstruct the transition object, i.e. the following won't work.
sel.getTransition().transition()...
So to simulate a subtransition that starts after the currently running one is complete, use setTimeout
to check whether something is running and as soon as nothing is, start your new transition:
function check() {
if(sel.getTransition() !== undefined) {
setTimeout(check, 100);
} else {
sel.transition().duration(1000)...;
}
}
check();
You can reduce the interval between checks (100ms here) to create a better impression of a transition that immediately follows the previous one.
Complete example here. Note that in almost all cases, it's far easier and better to keep a reference to the transition object somewhere and use that. This example really only serves as a hacky proof of concept.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With