简体   繁体   中英

How does each function and this keyword work together in d3?[chaining transitions]

I'm using the d3 library and having a hard time wrapping my head around some of the constructs, hoping for some guidance from experienced hands with this!

<!DOCTYPE html>
<html lang="en">
  <head>
<script src="http://d3js.org/d3.v3.min.js" charset="utf-8"></script>

  </head>
  <body>
  <script>
var svg=d3.select("body").append("svg").attr("width",800).attr("height",1000);
svg.selectAll("circle")
.data(d3.range(10)).enter().append("circle")
.attr("cx",function(d){return d;})
.attr("cy",function(d){return d*50;})
.attr("r",function(d){return d;})
.transition().duration(4000).style("fill","red")
.each("end",svg.selectAll(this).transition().duration(4000)
.attr("cx",function(d){return d*10;}));
</script>   
  </body>
  </html>

From what i understand, this is supposed to create circles, then transition the colour to fill them up with red, then change the "cx" attribute for each circle. Atleast, that's what i want to do. Can someone please explain how exactly method chaining works and how to use the each() function and this keyword?

In this case, your 'this' will not refer to any d3 object, because the this is evaluated within the same scope as your <script> tag, which, in non-strict mode, refers to the window object.

I suggest you try to start without the transition and then adding it gradually when you have the basics working.

The basic outline of your code is this:

  1. Generate a data set based on range 1-10
  2. Create a circle for each if these data objects
  3. All of the objects refer to their respective value, so they are in fact numbers, which you use to calculate their position(x, y) and size (r)
  4. Then you transition their background
  5. Then you tell it to listen to an "end" event and execute a piece of code.

Step 5 is where it fails. I think this is basically what you want:

var svg=d3.select("body").append("svg").attr("width",800).attr("height",1000);

svg.selectAll("circle")
   .data(d3.range(10))
   .enter()
      .append("circle") 
         .attr("cx",function(d){return d;})
         .attr("cy",function(d){return d*50;})
         .attr("r",function(d){return d;})
         .transition()
             .duration(4000)
             .style("fill","red")
             // attach a handler to all affected objects and listen
             // to the "end" event:
             .each("end", function() { console.log(this, arguments); })
         .transition()
             .duration(4000)
             .attr("cx",function(d){return d*10;})
             // do it again
             .each("end", function() { console.log(this, arguments); })
;

The transition effect is chained by default (see https://github.com/mbostock/d3/wiki/Transitions#transition )

In my experience, it helps a lot to think of a sensible indentation for d3, because it helps you understand what 'scope' were working on currently.

By the way, there is no random component in your code yet, so don't expect it to do any magic you don't tell it to do ;)

The way that you go about answering questions like this is in your debugger. To be completely sure of the answer, I extracted the inline function where I could set a breakpoint:

function resize() {
    svg.selectAll(this).transition().duration(4000) //set breakpoint here
        .attr("cx",function(d){return d*10;})
}  

var svg=d3.select("body").append("svg")
          .attr("width",800)
          .attr("height",1000);

svg.selectAll("circle")
    .data(d3.range(10)).enter().append("circle")
    .attr("cx",function(d){return d;})
    .attr("cy",function(d){return d*50;})
    .attr("r",function(d){return d;})
    .transition().duration(4000).style("fill","red")
    .each("end",resize);

When you set the breakpoint, you'll see that the value of this is probably what you expect it to be: the current DOM element, an '[object SVGCircleElement]' . You might also check the arguments and find that the parameters passed to resize are d and i . That is probably not a satisfying answer since your code doesn't work.

Your next question might be how to get resize to work correctly. I don't know why svg.selectAll(this) doesn't work, but if we call d3.select(this) , the method does work correctly.

function resize(d,i) {
    d3.select(this).transition().duration(4000)
        .attr("cx",function(d){return d*10;})
}

If you want to put resize back inline, don't forget to make your parameter a callback, not a statement:

.each("end",function (d) { d3.select(this).transition().duration(4000).attr("cx",d*10) });

Also, you can see that the documentation for transition.each([type],listener) says chained transitions ( transition.transition ) should be preferred. The standard way to implement the functionality you want is just chaining transitions:

.transition()
    .duration(4000)
    .style("fill","red")
.transition()
    .duration(4000)
    .attr("cx", function (d) { return d*10});

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