简体   繁体   中英

For loop does not work in D3.js

I'm new to Javascript and D3.js and trying to change a circle element continuously in a for loop. It's meant to change the radius of the circle every second but it turns out that the radius just jumps to 50, ignoring 26 to 49.

Here's my code.

<!DOCTYPE html>
<meta charset="utf-8">
<style>

</style>
<body>
<script src="//d3js.org/d3.v3.min.js" charset="utf-8"></script>
<script src="//d3js.org/topojson.v1.min.js"></script>
<script>
    var width = 960,
            height = 1160;
    var svg = d3.select("body").append("svg")
            .attr("width", width)
            .attr("height", height);

    svg.append("circle").attr("cx", 100).attr("cy", 100).attr("r", 25).style("fill", "purple").attr('id','cc');

    for(i=26;i<=50;i++){
        var now=d3.select('#cc');
        now.transition().duration(1000).attr("r",i);
    }
</script>

In JavaScript, when you say:

for(i=26;i<=50;i++){
    var now=d3.select('#cc');
    now.transition().duration(1000).attr("r",i);
}

Don't think that the for loop will wait for the i=26 transition to finish before it moves on to i=27 . In fact, it invokes the statement now.transition().duration(1000).attr("r",i); for i=26 and then IMMEDIATELY moves onto the next iteration of the loop. Since the time difference between the i=26 and i=50 iteration of the loop is infinitesimal (the speed limited only by the speed of the processor, which is in GHz today), you can think that ALL transitions are invoked together, and the effect of the last one is observed, immediately .

One way to do this is to set a time delay before each call, and remember, that time delay has to be ABSOLUTE, since all the iterations are called almost at the same instant of time. For example,

The first statement called after 0 seconds.
The second statement called after 1 seconds.
The third statement called after 2 seconds.

Only then will you see a relative difference of 1 second between the calls.

ANOTHER approach is to do it recursively using the setTimeout() routine. This approach is recommended.

var i = 26;

function myLoop () {
    setTimeout(function () {
        var now=d3.select('#cc');
        now.transition().duration(1000).attr("r",i);
        i++;
        if (i < 50) {
            myLoop();
        }
    }, 1000) // change this time (in milliseconds) to whatever you desire
}

myLoop(); // first call

The issue is that your for loop is just iterating through all those changes immediately. You need to wait 1 second in between each call to transition . Something like this should work, though perhaps d3 has a better way to do something after the current transition ends?

function changeRadius(i) {
    d3.select('#cc').transition().duration(1000).attr("r", i);

    // "loop" by calling ourselves 1 second later with the next radius
    if (i < 50) {
        window.setTimeout(function () {
            changeRadius(i + 1);
        }, 1000);
    }
}
// first change
changeRadius(26);

Looking at the d3 documentation , I think this may work:

var current = d3.select('#cc');
for (var i = 26; i <= 50; i++) {
    current = current.transition().duration(1000).attr("r", i);
}

Instead of creating multiple transitions or messing with delays you may tween radius. Demo .

d3.select('#cc')
  .transition()
  .duration(25*1000)
  .attrTween('r', function() {
      return d3.interpolateNumber(25, 50)
  })

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM