Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What is the standard way to get the active (running) D3 v3 Transition for a given element?

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.

like image 467
Chuck Avatar asked Dec 12 '12 16:12

Chuck


People also ask

What is d3 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.

What is attrTween?

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.


2 Answers

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.

like image 53
AmeliaBR Avatar answered Nov 16 '22 00:11

AmeliaBR


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.

like image 29
Lars Kotthoff Avatar answered Nov 15 '22 23:11

Lars Kotthoff