Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

D3.js: Stop transitions being interrupted?

I'm working with D3.js. I've got transitions working nicely, but I just have one problem: if a second transition starts before the first one ends,

This is a JSFiddle demonstrating the problem: http://jsfiddle.net/kqxhj/11/

It works fine most of the time - CDG and LAX get appended and removed as the data changes - but if you click the button twice in rapid succession, you'll notice that the new elements don't appear.

This is the meat of my code:

function update(data) { 

  var g = vis.selectAll("g.airport").data(data, function(d) { 
    return d.name;  
  });
  var gEnter = g.enter().append("g")
  .attr("class", function(d) {    
    return "airport " + d.name;
  });
  // Perform various updates and transitions... 
  [...]

  // Remove exited elements. 
  g.exit().transition()
    .duration(1000)
   .attr("transform", "translate(0," + 1.5*h + ")");
  g.exit().transition().delay(1000)
   .remove();
}

d3.select('#clickme').on("click", function() {  
  update(current_data); 
});

I've tried to add some debug statements to figure out what's going on, but all I can see is that when it happens, the exit selection has 4 elements in, not 3 - I don't understand why this is.

Is there a way, either in D3 or in basic JavaScript, that I can ensure the transitions don't overlap?

like image 687
Richard Avatar asked May 02 '13 10:05

Richard


2 Answers

UPDATE: Since the version 3.5 of D3 (October 2014), it is possible to perform concurrent transitions on elements through the use of named transitions. You simply have to add a different name to each transition.

like image 93
Pablo EM Avatar answered Nov 07 '22 04:11

Pablo EM


What's going on is that data representation "re-enters" before it has been removed from the DOM (because your remove() call is chained on a transition). However, if the data representation hasn't been removed from the DOM yet, the enter() selection will not contain that data because it already exists! And yet, your transition will continue to execute, and your data representation will disappear without having the chance to "re-enter".

What you need to do is give the exiting elements some sort of identifier. For example:

g.exit().classed('exiting', true);

Then, when you update your selection, if an element "re-entered", cancel the exiting transition and bring it back to its original state:

g.filter('.exiting')
    .classed('exiting', false)
    .transition() // new transition cancels the old one so that remove() isn't called
        .attr('foo', 'bar'); // return to original state

I've tweaked your fiddle to demonstrate the solution: http://jsfiddle.net/hX5Tp/

Here's a stripped down fiddle to demonstrate the issue (and solution) clearly: http://jsfiddle.net/xbfSU/

like image 24
Agop Avatar answered Nov 07 '22 04:11

Agop