turtle
turtle

Reputation: 8093

Difficulty understanding d3 version 4 selection life cycle with nested elements

In version d3 v3, a common workflow for me was to create several svg group elements and then append a number of other child elements to each g. as an example, below I've created 3 group elements and appended a circle to each group. I then use the selection.each method to update the radius of each circle:

   var data = [2, 4, 8]

   var g = d3.select('svg').selectAll('.g').data(data)
   g.each(function(datum) {
     var thisG = d3.select(this)
     var circle = thisG.selectAll('.circle')

     circle.transition().attr('r', datum * 2)
   })

   var enterG = g.enter().append('g').attr('class', 'g')
   enterG.append('circle')
     .attr('class', 'circle')
     .attr('r', function(d) { return d })

   g.exit().remove()

What is the proper way to do this in d3 v4? I am very confused on how best to do this. Here's an example of what i'm trying:

   var data = [2, 4, 8]

   var g = d3.select('svg').selectAll('.g').data(data)

   g.enter()
     // do stuff to the entering group
     .append('g')
     .attr('class', 'g')
     // do stuff to the entering AND updating group
     .merge(g)

    // why do i need to reselect all groups here to append additional elements?
    // is it because selections are now immutable?
    var g = d3.select('svg').selectAll('g')
    g.append('circle')
      .attr('class', 'circle')
      .attr('r', function(d) { return d })

   // for each of the enter and updated groups, adjust the radius of the child circles 
   g.each(function(datum) {
     var thisG = d3.select(this)
     var circle = thisG.selectAll('.circle')

     circle.transition().attr('r', datum * 2)
   })

   g.exit().remove()

Thanks in advance for any help you can provide. I've used d3 v3 for a long time and feel pretty comfortable with it. However, I am having a very hard time understanding some of the different behaviors in v4.

Upvotes: 0

Views: 455

Answers (1)

Hugues Stefanski
Hugues Stefanski

Reputation: 1182

I think your code could be modified as follow (untested, so unsure):

var data = [2, 4, 8]

var g = d3.select('svg').selectAll('.g').data(data);

// do stuff to the entering group
var enterSelection = g.enter();
var enterG = enterSelection.append('g')
 .attr('class', 'g');

//Append circles only to new elements
enterG.append('circle')
  .attr('class', 'circle')
  .attr('r', function(d) { return d })

// for each of the enter and updated groups, adjust the radius of the child circles 
enterG.merge(g)
  .select('.circle')
  .transition()
  .attr('r',function(d){return d*2});
g.exit().remove()

When using the first .selectAll, only existing elements are selected. Then, by entering, you are creating new elements, that generate a new selection. When you need to update all, you simply merge the new and existing elements in a single selection.

From that selection, I simply selected all .circle (single select - one element per g), and then update the radius thanks to the binding API that prevents me from making a .each call. I am unsure as how these two compares, I simply always did it this way.

Finally, here is a bl.ocks demonstrating the pattern.

Upvotes: 1

Related Questions