nachocab
nachocab

Reputation: 14364

How to correctly repeat nested transitions in d3?

Currently, the code below moves the squares in an undulating fashion, but I want to change it so there is a single traveling wave (like they do in the stadiums). I guess what I have to do is figure out how to add a delay to the repeat loop so the first square goes down and stays down until the last square starts to go up.

enter image description here

Here's my code and a jsfiddle:

var margin = {top: 40, bottom: 40, left: 40, right: 40},
    width = 960 - margin.left - margin.right,
    height = 200 - margin.bottom - margin.top;

var svg = d3.select("body").append("svg")
    .attr("width", width + margin.left + margin.right)
    .attr("height", height + margin.top + margin.bottom)
  .append("g")
    .attr("transform", "translate(" + margin.left + "," + margin.top + ")");

var n = 20,
    rect_width = 20,
    padding = 1, 
    speed = 1000,
    item_delay = 40;

var x = d3.scale.ordinal()
    .domain(d3.range(n))
    .rangePoints([0, n * (rect_width + padding)]);

var rects = svg.selectAll(".rect")
    .data(x.domain())
  .enter().append("rect")
    .attr("x", function(d,i){ return x(i); })
    .attr("y", 0)
    .attr("height", rect_width)
    .attr("width", rect_width)
    .attr("class", "rect")
  .transition() 
    .duration(speed)
    .delay(function(d,i){ return i * 120; })
    .each(start_repeat);

function start_repeat(){
    var rect = d3.select(this);
    (function repeat(){
        rect = rect.transition()
            .attr("y", 0)
          .transition()
            .attr("y", 100)
            .each("end", repeat);    
    })()
}

I think my problem is that I don't really understand what's going on inside the repeat function.

UPDATE

I figured out a simple to loop the transition, but it requires that I manually calculate the loop duration and I'd like to avoid this (see snippet for a live version).

speed = 500;
var loop_duration = 2000;
(function loop(){
rects.transition()
    .delay(function(d,i){ return i * item_delay; })
    .duration(speed)
    .attr("y", 100) // down
  .transition()
    .attr("y", 0)
setTimeout(loop, loop_duration)
})()    

var margin = {top: 40, bottom: 40, left: 40, right: 40},
    width = 960 - margin.left - margin.right,
    height = 300 - margin.bottom - margin.top;

var svg = d3.select("body").append("svg")
    .attr("width", width + margin.left + margin.right)
    .attr("height", height + margin.top + margin.bottom)
  .append("g")
    .attr("transform", "translate(" + margin.left + "," + margin.top + ")");

var n = 20,
    rect_width = 20,
    padding = 1, 
    speed = 500,
    item_delay = 100;

var x = d3.scale.ordinal()
    .domain(d3.range(n))
    .rangePoints([0, n * (rect_width + padding)]);

var rects = svg.selectAll(".rect")
    .data(x.domain())
  .enter().append("rect")
    .attr("x", function(d,i){ return x(i); })
    .attr("height", rect_width)
    .attr("width", rect_width)
    .attr("class", "rect")
    .attr("y", 0); // up - very important, if you don't set this, there is no interpolation

var loop_duration = 2000;
(function loop(){
    rects.transition()
        .delay(function(d,i){ return i * item_delay; })
        .duration(speed)
        .attr("y", 100) // down
      .transition()
        .attr("y", 0)
    setTimeout(loop, loop_duration)
})()
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>

I've tried deparsing the start_repeat function to understand what's going on, but I still don't get it. For example, what is the difference between old_rect and rect?

function start_repeat() {
  var rect = d3.select(this);
  (function repeat() {
    var move_down_t0 = rect.transition() // chain the down-transition on the same selection
        .attr("y", 100);

     var move_up_t1 = move_down_t0.transition()
        .attr("y", 0)

     old_rect = rect;
     rect = move_up_t1.each("end", repeat);
  })();
}

Upvotes: 3

Views: 1942

Answers (2)

AvgustinTomsic
AvgustinTomsic

Reputation: 1841

My solution:

http://jsfiddle.net/k5zm18hn/2/

function repeat(){
    if(d3.select(this).attr("y")==0){
        d3.select(this).transition()
            .delay(pausing_delay)
            .duration(speed)
            .attr("y", 100)
            .each("end", repeat);
    }
    else{
        d3.select(this).transition()
            .attr("y", 0)
            .duration(speed)
            .each("end", repeat);
    }
}

Change pausing_delay variable for optimal results.

Upvotes: 1

Union find
Union find

Reputation: 8150

I am a bit confused myself, but I tried setting the duration and delays to be equal, such that the transition lasts as long as the values are set down.

Try this:

var rects = svg.selectAll(".rect")
    .data(x.domain())
  .enter().append("rect")
    .attr("x", function(d,i){ return x(i); })
    .attr("y", 0)
    .attr("height", rect_width)
    .attr("width", rect_width)
    .attr("class", "rect")
  .transition() 
    .duration(speed)
    .delay(function(d,i){ return i * (speed/(n*2)); })
    .each(start_repeat);

Upvotes: 1

Related Questions