To create this DOM:
<g>
<rect></rect>
<circle></circle>
</g>
from .enter()
selection, I tried:
someUpdate.enter()
.append('g')
.attr('class', 'my-group')
.append('rect')
.attr('class', 'my-rect')
// I'd like to get the .parent() here
.append('cicle')
.attr('class', 'my-circle')
This doesn't work since .append('rect')
changes the selection to rect
.
Breaking this to:
const update = someUpdate.enter()
.append('g')
.attr('class', 'my-group')
update
.append('rect')
.attr('class', 'my-rect')
update
.append('cicle')
.attr('class', 'my-circle')
works.
But, I wonder if there is a cleaner way?
Approach: Write a recursive function that takes the current node and its parent as the arguments (root node is passed with -1 as its parent). If the current node is equal to the required node then print its parent and return else call the function recursively for its children and the current node as the parent.
Selection methods come in two forms: select and selectAll: the former selects only the first matching element, while the latter selects all matching elements in document order. The top-level selection methods, d3.
select() function in D3. js is used to select the first element that matches the specified selector string. If any element is not matched then it returns the empty selection. If multiple elements are matched with the selector then only the first matching element will be selected.
There are no methods in D3 for traversing the DOM like e.g. jQuery's .parent()
. Hence, the way you broke this down into separate statements will be the correct approach.
On the other hand, it is not completely impossible to do it the way you first suggested. Just yesterday I posted an answer to "D3.js - what is selection.call() returning?" explaining how selection.call()
will return exactly the selection it was called upon to allow for method chaining. Keeping that in mind you could something like the following:
d3.select("svg").selectAll("g")
.data([1])
.enter().append('g')
.call((parent) => parent.append('rect')
.attr("fill", "red")
.attr("width", 100).attr("height", 100))
.call((parent) => parent.append('circle')
.attr("fill", "blue").attr("r", 50));
<script src="https://d3js.org/d3.v4.js"></script>
<svg></svg>
Both functions invoked by .call()
will be passed the same selection of previously entered <g>
elements, which happens to be the parent element in this case.
Although it is possible to do it this way, the solution has its drawbacks. First, it will look somewhat strange and awkward to any seasoned D3 developer, which might complicate matters if you want to share or discuss your code with others. Second, even though I named the parameter parent
, which it is in this particular case, it is still not really an equivalent to jQuery's .parent()
method. It will just pass in and return the very same selection be it a parent selection or something else.
Agree with the others that your second code snippet is the correct way to do what you want but I want to play to, how about:
d3.select("svg").selectAll("g")
.data([1])
.enter()
.append('g')
.each(function() {
var p = d3.select(this);
p.append('rect')
.attr("fill", "red")
.attr("width", 100).attr("height", 100);
p.append('circle')
.attr("fill", "blue").attr("r", 50);
});
<script src="https://d3js.org/d3.v4.js"></script>
<svg></svg>
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