d3.js - v3 and v4 - Enter and Update differences

后端 未结 2 1272
长发绾君心
长发绾君心 2020-11-28 16:37

I\'m trying to get the values for x and y to make a circles using d3.js v4. With the following code I manage to create the chart like behavior of t

相关标签:
2条回答
  • 2020-11-28 17:16

    This is the expected behaviour, and I've explained this before in this answer (not a duplicate, though).

    What happened is that Mike Bostock, D3 creator, introduced a magic behaviour in D3 v2, which he kept in D3 v3.x, but decided to abandon in D3 v4.x. To read more about that, have a look here: What Makes Software Good? This is what he says:

    D3 2.0 introduced a change: appending to the enter selection would now copy entering elements into the update selection [...] D3 4.0 removes the magic of enter.append. (In fact, D3 4.0 removes the distinction between enter and normal selections entirely: there is now only one class of selection.)

    Let's see it.

    Here is your code with D3 v3:

    var svg = d3.select('body').append('svg')
      .attr('width', 250)
      .attr('height', 250);
    
    //render the data
    function render(data) {
      //Bind 
      var circles = svg.selectAll('circle').data(data);
    
      //Enter
      circles.enter().append('circle')
        .attr('r', 10);
      //Update
      circles
        .attr('cx', function(d) {
          return d.x;
        })
        .attr('cy', function(d) {
          return d.y;
        });
    
      //Exit
      circles.exit().remove();
    }
    
    var myObjects = [{
      x: 100,
      y: 100
    }, {
      x: 130,
      y: 120
    }, {
      x: 80,
      y: 180
    }, {
      x: 180,
      y: 80
    }, {
      x: 180,
      y: 40
    }];
    
    
    render(myObjects);
    <script src='https://d3js.org/d3.v3.min.js'></script>

    Now the same code, with D3 v4. It will "break":

    var svg = d3.select('body').append('svg')
      .attr('width', 250)
      .attr('height', 250);
    
    //render the data
    function render(data) {
      //Bind 
      var circles = svg.selectAll('circle').data(data);
    
      //Enter
      circles.enter().append('circle')
        .attr('r', 10);
      //Update
      circles
        .attr('cx', function(d) {
          return d.x;
        })
        .attr('cy', function(d) {
          return d.y;
        });
    
      //Exit
      circles.exit().remove();
    }
    
    var myObjects = [{
      x: 100,
      y: 100
    }, {
      x: 130,
      y: 120
    }, {
      x: 80,
      y: 180
    }, {
      x: 180,
      y: 80
    }, {
      x: 180,
      y: 40
    }];
    
    
    render(myObjects);
    <script src='https://d3js.org/d3.v4.min.js'></script>

    By "break" I mean the circles will be appended, but they will not receive the x and y properties in the "enter" selection, and they will default to zero. That's why you see all circles at the top left corner.

    Solution: merge the selections:

    circles.enter().append('circle')
      .attr('r', 10)
      .merge(circles) //from now on, enter + update
      .attr('cx', function(d) {
        return d.x;
      })
      .attr('cy', function(d) {
        return d.y;
      });
    

    According to the API, merge()...

    ... is commonly used to merge the enter and update selections after a data-join. After modifying the entering and updating elements separately, you can merge the two selections and perform operations on both without duplicate code.

    Here is the code with merge():

    var svg = d3.select('body').append('svg')
      .attr('width', 250)
      .attr('height', 250);
    
    //render the data
    function render(data) {
      //Bind 
      var circles = svg.selectAll('circle').data(data);
    
      //Enter
      circles.enter().append('circle')
        .attr('r', 10)
        .merge(circles) //from now on, enter + update
        .attr('cx', function(d) {
          return d.x;
        })
        .attr('cy', function(d) {
          return d.y;
        });
    
      //Exit
      circles.exit().remove();
    }
    
    var myObjects = [{
      x: 100,
      y: 100
    }, {
      x: 130,
      y: 120
    }, {
      x: 80,
      y: 180
    }, {
      x: 180,
      y: 80
    }, {
      x: 180,
      y: 40
    }];
    
    
    render(myObjects);
    <script src='https://d3js.org/d3.v4.min.js'></script>

    0 讨论(0)
  • 2020-11-28 17:16

    Update pattern was changed in d3v4. Excerpt from the documentation:

    In addition, selection.append no longer merges entering nodes into the update selection; use selection.merge to combine enter and update after a data join.

    You should rewrite your code this way:

    var svg = d3.select('body').append('svg')
                .attr('width', 250)
                .attr('height', 250);
    
        //render the data
        function render(data){
                //Bind 
                var circles = svg.selectAll('circle').data(data);
    
                //Enter
                circles.enter().append('circle')
                    .attr('r', 10).merge(circles) // <== !!!
                    .attr('cx', function(d) { return d.x; })
                    .attr('cy', function(d) { return d.y; });
    
    
                //Exit
                circles.exit().remove();
        }
    
       
    
        var myObjects = [
            {x: 100, y: 100},
            {x: 130, y: 120},
            {x: 80, y: 180},
            {x: 180, y: 80},
            {x: 180, y: 40}
        ];
        
    
        render(myObjects);
    <script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.11.0/d3.min.js"></script>

    0 讨论(0)
提交回复
热议问题