Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

d3: How to properly chain transitions on different selections

I am using V3 of the popular d3 library and basically want to have three transitions, followed by each other: The first transition should apply to the exit selection, the second to the update selection and the third to the enter selection. They should be chained in such a manner that when one of the selections is empty, its respective transition is skipped. I.e. when there is no exit selection, the update selection should start immediately. So far, I have come up with this code (using the delay function).

// DATA JOIN
var items = d3.select('#data').selectAll('.item');
items = items.data(data, function(d){ 
    return d.twitter_screenname;
});


// EXIT
items.exit().transition().duration(TRANSITION_DURATION).style('opacity', 0).remove();

// UPDATE
// Divs bewegen
items.transition().duration(TRANSITION_DURATION).delay(TRANSITION_DURATION * 1)
    .style('left', function(d, i) {
        return positions[i].left + "px";
    }).style('top', function(d, i) {
        return positions[i].top + "px";
    });

// ENTER
// Divs hinzufügen
var div = items.enter().append('div')
    .attr('class', 'item')
    .style('left', function(d, i) {
        return positions[i].left + "px";
    }).style('top', function(d, i) {
        return positions[i].top + "px";
    });

 div.style('opacity', 0)
    .transition().duration(TRANSITION_DURATION).delay(TRANSITION_DURATION * 2)
    .style('opacity', 1);

First of all it doesn't allow to "skip" transitions and secondly I think there is a better way than delay. I've looked at http://bl.ocks.org/mbostock/3903818 but I did not really understand what is happening.

Also, somehow just writing items.exit().transition().duration(TRANSITION_DURATION).remove() does not work with the items, probably because they are not SVG elements but divs.

like image 721
grssnbchr Avatar asked Jun 13 '13 11:06

grssnbchr


1 Answers

Sure. Here are two ways.

First, you could use an explicit delay, which you then compute using selection.empty to skip empty transitions. (This is only a minor modification of what you have already.)

var div = d3.select("body").selectAll("div")     .data(["enter", "update"], function(d) { return d || this.textContent; });  // 2. update div.transition()     .duration(duration)     .delay(!div.exit().empty() * duration)     .style("background", "orange");  // 3. enter div.enter().append("div")     .text(function(d) { return d; })     .style("opacity", 0)   .transition()     .duration(duration)     .delay((!div.exit().empty() + !div.enter().empty()) * duration)     .style("background", "green")     .style("opacity", 1);  // 1. exit div.exit()     .style("background", "red")   .transition()     .duration(duration)     .style("opacity", 0)     .remove(); 

http://bl.ocks.org/mbostock/5779682

One tricky thing here is that you have to create the transition on the updating elements before you create the transition on the entering elements; that’s because enter.append merges entering elements into the update selection, and you want to keep them separate; see the Update-only Transition example for details.

Alternatively, you could use transition.transition to chain transitions, and transition.each to apply these chained transitions to existing selections. Within the context of transition.each, selection.transition inherits the existing transition rather than creating a new one.

var div = d3.select("body").selectAll("div")     .data(["enter", "update"], function(d) { return d || this.textContent; });  // 1. exit var exitTransition = d3.transition().duration(750).each(function() {   div.exit()       .style("background", "red")     .transition()       .style("opacity", 0)       .remove(); });  // 2. update var updateTransition = exitTransition.transition().each(function() {   div.transition()       .style("background", "orange"); });  // 3. enter var enterTransition = updateTransition.transition().each(function() {   div.enter().append("div")       .text(function(d) { return d; })       .style("opacity", 0)     .transition()       .style("background", "green")       .style("opacity", 1); }); 

http://bl.ocks.org/mbostock/5779690

I suppose the latter is a bit more idiomatic, although using transition.each to apply transitions to selections (rather than derive transitions with default parameters) isn’t a widely-known feature.

like image 115
mbostock Avatar answered Oct 25 '22 03:10

mbostock