score:4

Accepted answer

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.

score:2

@altocumulus said in his answer:

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.

Well, just as him, I have to say that OP's original code (appending the rectangle and the circle to the group separately) is the standard way, applied by the majority of D3 coders.

But, just for fun and to participate in this strange competition, here is another way to do it, using the less famous third argument:

d3.select("svg").selectAll("g")
    .data([1])
    .enter()
    .append('g')
    .attr("foo", function(d, i, p) {
        d3.select(p[0]).append('rect')
            .attr("fill", "red")
            .attr("width", 100).attr("height", 100);
        return null;
    })
    .attr("foo", function(d, i, p) {
        d3.select(p[0]).append('circle')
            .attr("fill", "blue").attr("r", 50);
        return null;
    });
<script src="https://d3js.org/d3.v4.js"></script>
<svg></svg>

I bet that this wins the "awkward code" competition.

score:3

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>


Related Query

More Query from same tag