Search code examples
javascriptd3.jsevent-handlingdelaytransition

D3: event functions with concatenated transitions


I have a code like the following:

selection.
    .transition()
    .delay(1000)
    .each("start",f1)
    ...
    .transition()
    .delay(2000)
    .each("start",f2)
    ...
    .transition()
    .delay(3000)
    .each("start",f3)
    ...

Functions f1, f2 and f3 change the appearance of some DOM elements in the page.

I need to run functions f1, f2, f3 when the transitions start to yield effects, that is, after the time specified by delay.

But first of all I need to run function f1, f2 and f3 when the corresponding transitions take place.

Instead It seems to me that f3 is executed immediately after f1 and f2 and so it hides their DOM changes.

What's the right thing to do?


Solution

  • If your functions are running once and all at once without regard for the listeners, it is possibly because you are placing brackets after your function when attaching your listener:

    .each('start',function());
    

    While you have not indicated this in your question, you may have excluded them for a cleaner question.

    If you need to pass parameters to a function you'll need to actually write an inline function to call up your function:

    .each('start', function() { functionName(param1,param2); })
    

    Even the brackets are not the source of troubles, I hope the examples below might help.

    I've used d3.js v4 in my answer: the .on method is used here instead of .each

    While I have used duration instead of delay, the answer should still be applicable in my brief testing.


    Snippets:

    Example based on your question (assuming brackets are cause of headaches):

    var svg = d3.select('body').append('svg').attr('width',400).attr('height',200);
    
    var data = [4,12,4,12,4,12,4];
    
    svg.selectAll('circle')
      .data(data)
      .enter()
      .append('circle')
      .attr('cx', function(d,i) { return 40 + (i * 20); })
      .attr('cy', 50)
      .attr('r', function(d) { return d; })
      .attr('fill','black');
      
    
    svg.selectAll('circle')
      // Transition 1
      .transition()
      .attr('r', function(d) { return (d == 4) ? 10 : 4; })
      .duration(3000)
      .on('start', s1() )
      .on('end', e1() )
    
      // Transition 2
      .transition()
      .attr('fill', function(d) { return (d == 4) ? "steelblue" : "orange"; })
      .duration(3000)
      .on('start', s2() )
      .on('end', e2() )
      
      // Transition 3
      .transition()
      .attr('cy', function(d,i) { return (d==4) ? 30:60; })
      .attr('r', function(d) { return (d == 4) ? 14 : 18; })
      .duration(3000)
      .on('start', s3() )
      .on('end', e3() )
      ;
      
      function s1() {  console.log("Transition 1 Start");  }
      function s2() {  console.log("Transition 2 Start");  }
      function s3() {  console.log("Transition 3 Start");  }
      function e1() {  console.log("Transition 1 End");    }
      function e2() {  console.log("Transition 2 End");    }  
      function e3() {  console.log("Transition 3 End");    }
     <script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.6.0/d3.js"></script>

    All functions trigger at once, and they trigger once.


    Removing the brackets will give you:

    var svg = d3.select('body').append('svg').attr('width',400).attr('height',200);
    
    var data = [4,12,4,12,4,12,4];
    
    svg.selectAll('circle')
      .data(data)
      .enter()
      .append('circle')
      .attr('cx', function(d,i) { return 40 + (i * 20); })
      .attr('cy', 50)
      .attr('r', function(d) { return d; })
      .attr('fill','black');
      
    
    svg.selectAll('circle')
      // Transition 1
      .transition()
      .attr('r', function(d) { return (d == 4) ? 10 : 4; })
      .duration(3000)
      .on('start', s1 )
      .on('end', e1 )
    
      // Transition 2
      .transition()
      .attr('fill', function(d) { return (d == 4) ? "steelblue" : "orange"; })
      .duration(3000)
      .on('start', s2 )
      .on('end', e2 )
      
      // Transition 3
      .transition()
      .attr('cy', function(d,i) { return (d==4) ? 30:60; })
      .attr('r', function(d) { return (d == 4) ? 14 : 18; })
      .duration(3000)
      .on('start', s3 )
      .on('end', e3 )
      ;
      
      function s1() {  console.log("Transition 1 Start");  }
      function s2() {  console.log("Transition 2 Start");  }
      function s3() {  console.log("Transition 3 Start");  }
      function e1() {  console.log("Transition 1 End");    }
      function e2() {  console.log("Transition 2 End");    }  
      function e3() {  console.log("Transition 3 End");    }
     <script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.6.0/d3.js"></script>

    There is no transition for the selection, each element is individually transitioned, resulting in the multiple calls to each function (this may be desired, I'm unsure if it is in your case).


    If you want to trigger each function once, then you will need to count how many elements have finished transitioning (I've done this for the end events below). Or if you are confident all transitions will finish nearly simultaneously, you could call the function once per selection by using an if statement that triggers the event when a particular element starts or finishes (I've done this for the start events below):

    var svg = d3.select('body').append('svg').attr('width',400).attr('height',200);
    
    var data = [4,12,4,12,4,12,4];
    
    svg.selectAll('circle')
      .data(data)
      .enter()
      .append('circle')
      .attr('cx', function(d,i) { return 40 + (i * 20); })
      .attr('cy', 50)
      .attr('r', function(d) { return d; })
      .attr('fill','black');
      
    
    var n = 6;
    var m = 6;
    
    svg.selectAll('circle')
      // Transition 1
      .transition()
      .on('start',function(d,i) { if (i == 6) { log('transition 1 started'); } })
      .on('end',function(d,i) { if(--m == 0) { m = 6; log('transition 1 ended'); } })
      .attr('r', function(d) { return (d == 4) ? 10 : 4; })
      .duration(3000)
    
      // Transition 2
      .transition()
      .on('start',function(d,i) { if (i == 6) { log('transition 2 started'); } })
      .on('end',function(d,i) { if(--m == 0) { m = 6; log('transition 2 ended'); } })
      .attr('fill', function(d) { return (d == 4) ? "steelblue" : "orange"; })
      .duration(3000)
      
      // Transition 3
      .transition()
      .on('start',function(d,i) { if (i == 6) { log('transition 3 started'); } })
      .on('end',function(d,i) { if(--m == 0) { m = 6; log('transition 3 ended'); } })
      .attr('cy', function(d,i) { return (d==4) ? 30:60; })
      .attr('r', function(d) { return (d == 4) ? 14 : 18; })
      .duration(3000)
      ;
      
      function log(string) {
        console.log(string);
      }
    <!--<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>-->
    <script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.6.0/d3.js"></script>