0

I just asked a similar question before, but now I decided to change it a little bit into a new question.

I use d3.js. The array called A stores 3 colors and I want to go through a loop that fills my element link. Unfortunately this loop is so fast, only the very last element gets visible on screen, and that means only the color green.

How can I delay this process? That means turn the links blue, wait 2 seconds, turn them red, wait another 2 seconds and at last turn them green?

Here's my code...

var A = ["blue", "red", "green"]

for (var i = 0; i < A.length; i++){
    link.style("stroke", function(d){
       return A[i];
    }) 
 };
jacefarm
  • 5,123
  • 5
  • 32
  • 44
Derick Kolln
  • 553
  • 4
  • 14

5 Answers5

1

If you dont mind a ES8 answer (cool but very new stuff):

//a simple timer
var time=ms=>new Promise(res=>setTimeout(res,ms));

//the main function
async function loop(){
  var A = ["blue", "red", "green"]
  for ( var i = 0; i < A.length; i++){
     link.style("stroke", function(d) { return A[i];   }) 
     //here comes the magic part
     await time(5000);//wait 5 seconds
  }        
}
loop();

Or using an pseudorecursive timer (including ES6 object destructuring):

(function iterate([current,...rest]){
  if(!current) return;
  link.style("stroke", function(d) { return current;   })
  setTimeout(iterate,5000,rest);
})(["blue", "red", "green"]);
Jonas Wilms
  • 106,571
  • 13
  • 98
  • 120
1

Taken from W3Schools:

setTimeout(function(){ alert("Hello"); }, 3000);

What this does is wait 3 seconds, then alert 'hello.'

Using this concept, you can rewrite your loop into recursion, and then use the setTimeout to chain it off with a delay of 2 seconds or so. Your base case would be the last color, in which you do not want to set a time out.

Joe Bernstein
  • 163
  • 1
  • 10
  • The issue with that is that the JS engine doesn't guarantee what order that they will be executed. They'll generally come back in the order you want, but it's not for sure. – Adam LeBlanc Jul 26 '17 at 17:20
  • 1
    Even if one of the setTimeouts() is the one that calls the subsequent one? So it isn't even being set until the prior one is completed? – Joe Bernstein Jul 26 '17 at 17:24
  • 1
    That's not how `setTimeout` works. It's an async function, so when you call it it first get's pushed onto the "web api" place, which is where the timer ticks down, not on the actual stack. Then when the timer ticks down it pushes the callback into the queue, then when the stack is empty, the queue starts to empty, one at a time. Check out this visualisation http://latentflip.com/loupe/ – Adam LeBlanc Jul 26 '17 at 17:29
  • There's nothing to say that one timer won't finish early or get stuck for some reason. So you can't just use `setTimeout` and expect the order to be consistent. – Adam LeBlanc Jul 26 '17 at 17:33
  • Ah I see, thanks for the explanation. – Joe Bernstein Jul 26 '17 at 17:39
  • @adam leblanc what has this to do with this answer?? this doesnt apply to 5 secs timers – Jonas Wilms Jul 26 '17 at 17:47
  • @joe bernstein yes thats right – Jonas Wilms Jul 26 '17 at 17:48
1

You could set multiple timeouts:

var A = ["blue", "red", "green"]

for ( var i = 0; i < A.length; i++){
    setTimeout(function(){
        link.style("stroke", function(d) {
            return A[i];
        });
    }, 2000*i)
};
Jonas Wilms
  • 106,571
  • 13
  • 98
  • 120
Joge
  • 119
  • 2
1
delay = 2000;
var i=0;
var handle = setInterval( function() {
    if (i >= A.length) {
        clearInterval(handle);
    } else {
        link.style("stroke", function(d) { return A[i++]});
    }
}, delay);
Jarek Kulikowski
  • 1,389
  • 5
  • 9
1

There are quite a few plain javascript answers, since you are using d3, I'll offer a way to use d3 to achieve this effect (with which I've included a transition):

var svg = d3.select("body")
  .append("svg")
  .attr("width",400)
  .attr("height",400);
  
var circle = svg.append("circle")
  .attr("cx",100)
  .attr("cy",100)
  .attr("r",20)
  .attr("stroke","black");
  
var colors = ["orange","steelblue","lawngreen","pink","darkgreen","purple"];


var i = 0
transition(i);

function transition(i) {
  if (colors[i]) {
  circle.transition()
    .attr("fill",function() { return colors[i]; })
    .duration(1000)
    .each("end", function() { transition(++i) });
  }
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>

The .each method (v3) is now .on (v4), and is invoked on each transition end (for each element), so if transitioning multiple elements, you would need to check to see how many elements have finished transitioning:

var svg = d3.select("body")
  .append("svg")
  .attr("width",400)
  .attr("height",400);
  
var circles = svg.selectAll("circle")
  .data([1,2])
  .enter()
  .append("circle")
  .attr("cx",function(d) { return d * 100; })
  .attr("cy",100)
  .attr("r",20)
  .attr("stroke","black");
  
var colors = ["orange","steelblue","lawngreen","pink","darkgreen","purple"];


var i = 0
transition(i);

function transition(i) {
  var n = 0; // # of elements done this transition
  if (colors[i]) {
  circles.transition()
    .attr("fill",function() { return colors[i]; })
    .duration(1000)
    .each("end", function() { if (++n == circles.size()) { transition(++i) } });
  }
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
Andrew Reid
  • 30,848
  • 7
  • 44
  • 62