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 wele 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.
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 wele 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.
Share Improve this question edited Dec 14, 2013 at 15:47 Chuck asked Dec 12, 2012 at 16:28 ChuckChuck 1,07213 silver badges28 bronze badges 3- Can you specify if you are talking about v2 or v3? – Wex Commented Dec 13, 2012 at 1:14
- I said version 3, but maybe I should bold it or something.. – Chuck Commented Dec 13, 2012 at 15:58
- Inspecting _transition_ is probably the most efficient way to do it. The _transition_ property is removed after the transition on an element ends, so, you could simply do a check to see if the _transition_ property is present. If it is, you know there is a transition happening on that element,and you can check _transition_ to get more detailed information. You also have options for grabbing sub-elements of the transition element, such as using transition.selectAll(). You could also add listeners to each element in transition using transition.each() – Matthew Herbst Commented Jan 27, 2014 at 4:56
2 Answers
Reset to default 4Another 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 plication 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/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 plete, 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.