I am parsing an xml string and display it as a tree in d3.js. I can build the whole tree in the 'enter' step, but now I want to make it interactive and pull out the configuration into an update step. I'm following https://stackoverflow.com/a/24912466 to implement the general update pattern but I can't seem to set the attribute to the g element outside the enter step:
const svg = d3.select("#canvas");
const xmlAsText = `
<root attrib1="foo" att2="foO">
<child1 attrib1="ba">zwei</child1>
<child2>eins</child2>
</root>`;
treeDataXml = (new DOMParser()).parseFromString(xmlAsText, "text/xml");
let hierarchy = d3.hierarchy(treeDataXml.children[0], d => d.children);
nodesData = d3.tree()(hierarchy);
var myGroups = svg.selectAll("g").data(nodesData.descendants());
myGroupsEnter = myGroups.enter().append("g")
// 1) works
//myGroupsEnter .attr("class", "x");
// 2) doesn't work
myGroups.select("g").attr("class", "x");
console.log(document.getElementById("canvas").childNodes[0].attributes[0])
alert(document.getElementById("canvas")
.childNodes[0].attributes.length)
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<svg id="canvas"></svg>
with 1) I get
<body>
<svg id="canvas">
<g ></g>
<g ></g>
<g ></g>
</svg>
</body>
but with 2) it's just:
<body>
<svg id="canvas">
<g></g>
<g></g>
<g></g>
</svg>
</body>
I would expect that with myGroups.select("g").attr("class", "x")
the previously enter
ed <g>
s will all be selected and have their attribute class
set to "x". Why doesn't this work? How can I fix this?
CodePudding user response:
Selections are immutable, myGroups
is an empty selection: it doesn't contain any DOM elements yet. As this selection is the update selection, this makes sense, there is nothing to update.
The selection myGroupsEnter
contains the newly created elements. Entering elements does not modify myGroups
, this remains an empty selection. This explains why myGroupsEnter.attr("class", "x");
works and myGroups.select("g").attr("class", "x");
does not.
D3 v3 and earlier added newly entered elements in the update selection, which is why some examples might be misleading, but as this behavior was not explicit it was removed
Often you want to combine the entered and updated elements into one selection, you can use:
let combined = myGroups.merge(myGroupsEnter);
This way regardless of whether you are entering all elements, updating all elements, or entering some and updating others you can modify all elements that exist (that are not exited).