I can't work out how best to pass changes to the data which occur at a parent node (e.g. an SVG g
element) down to it's children (e.g. SVG circle
elements).
I've read this and this but still can't figure it out.
Here's a minimum working example. The example assumes you've got an object called svg
which refers to a d3 selection containing an SVG element.
data = [{"id":"A","name":"jim"},{"id":"B","name":"dave"},{"id":"C","name":"pete"}]; g = svg.selectAll("g").data(data, function(d) { return d.id; }).enter().append("g"); g.append("circle") .attr("r", 3) .attr("cx", 100) .attr("cy", function(d,i) {return 100 + (i * 30);}) // The data gets passed down to the circles (I think): console.log("circle data:"); d3.selectAll("g circle").each(function(d) { console.log(d.name); }); // Now change the data, and update the groups' data accordingly data = [{"id":"A","name":"carol"},{"id":"B","name":"diane"},{"id":"C","name":"susan"}]; svg.selectAll("g").data(data, function(d) { return d.id;}); // These are the results of the change: console.log("after change, the group has:"); d3.selectAll("g").each(function(d) { console.log(d.name); }); console.log("but the circles still have:"); d3.selectAll("g circle").each(function(d) { console.log(d.name); });
Can anyone help me find a concise way to get the new names into all the child elements of a group? In my real-life example, each g
contains many circle
s.
There are 2 ways to propagate the data from parents to children:
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")
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
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 ]
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With