score:34

Accepted answer

There are 2 ways to propagate the data from parents to children:

  1. selection.select will do this implicitly. (The implementations of selection.append and selection.insert are actually based on selection.select internally)

    svg.selectAll("g").select("circle")
    
  2. You can explicitly redo the data join using a function to receive the parent data and return it in an array for the child.

    svg.selectAll("g").selectAll("circle")
        .data(function(d) { return [d]; });
    

These amount to the same thing. The first option relies on some special behaviour in select so it can be a bit surprising at first, but it is nice in that it makes the pattern for node update symmetrical with the pattern for node creation via insert/append. The second option is useful if you need to apply any changes to the data as it is being propagated.

Here's another article you didn't link to that might be useful also: Thinking with Joins

score:0

To expand on Scott Cameron answer, with the join syntax the answer would be:

const groups = svg.selectAll('g').data(data, (d) => d.id).enter().append('g');

groups.selectAll('circle')
      .data(d => [ d ])
      .join(
            enter => enter.append('circle')
                           .attr(...)
                           ...,
            update => update.attr('cx', (d) => ...)
                            .attr(...)
                            ...,               
      );

If you want to add another element to each group you can copy the 2nd block. You can even conditionally add the sub element:

groups.selectAll('rect')
      .data(d => someConditionHere ? [ d ] : []) // if we return empty array the element won't be added
      .join(
            enter => enter.append('rect')
                           .attr(...)
                           ...,
            update => update.attr('x', (d) => ...)
                            .attr(...)
                            ...,               
      );

you can find more informations about nested selections here: https://bost.ocks.org/mike/nest/

score:3

Not sure if you figured it out, but this is definitely not in the documentation. All of the documentation that deals with element grouping does not seem to deal with the child selection and data inheritance to children.

The answer is to use the .each construct to update the children elements and append the children to the group enter() call.

data = [{"id":"A","name":"jim"},{"id":"B","name":"dave"},{"id":"C","name":"pete"}];

function draw(data) {
  var g = svg.selectAll("g").data(data, function(d) { return d.id; })

  genter = g.enter().append("g");

  // This is the update of the circle elements - 
  // note that it is attached to the g data, not the enter()
  // This will update any circle elements that already exist
  g.each(function(d, i) {
    var group = d3.select(this);
    group.select("circle")
    .transition() 
      .attr("r", 3)
      .attr("cx", 100)
      .attr("cy", function(d,i) {return 100 + (i * 30);})
  }

  // The data DOES get passed down to the circles, and the enter() statement
  // will create a circle child for each data entry
  genter.append("circle")
      .attr("r", 3)
      .attr("cx", 100)
      .attr("cy", function(d,i) {return 100 + (i * 30);})
}

// Original drawing
draw(data);

// Now change the data, and update the groups' data accordingly
data = [{"id":"A","name":"carol"},{"id":"B","name":"diane"},{"id":"C","name":"susan"}];

// Second drawing, the SVG will be updated
draw(data);

Let me know if this works.

[This answer is based on some code grouping from this coderwall post: https://coderwall.com/p/xszhkg ]


Related Query