Suppose we have a svg element with no child nodes:
<svg id="bargraph" width="400" height="90" ></svg>
Suppose we also have an array of data:
var data = [10,20,30,40,50];
This code properly append new rect elements to the svg element.
d3.select("#bargraph")
.selectAll("rect")
.data(data)
.enter()
.append("rect")
.attr("height", 10)
.attr("width", function(d) { return d; })
.attr("x", 0)
.attr("y", function (d, i) { return i * 20; })
.attr("fill", "blue");
The code below does not append new rect elements to the svg element. Can you tell me why?
d3.selectAll("#bargraph rect")
.data(data)
.enter()
.append("rect")
.attr("height", 10)
.attr("width", function(d) { return d; })
.attr("x", 0)
.attr("y", function (d, i) { return i * 20; })
.attr("fill", "blue");
Whenever you append elements, D3 appends them to a selected parent element(s). You aren't selecting a parent element, instead you are only selecting elements that have a specified parent.
In your example, you are selecting all rect
that have a parent with id bargraph
, while you can update those nodes successfully with:
d3.selectAll("#bargraph rect").data(data).attr(...)
But, using the following won't append items to #bargraph
(as you state):
d3.selectAll("#bargraph rect").data(data).enter()
Here, D3 will scan the entire document, the parent element, (d3.selectAll()
) and return each matching element that matches the selector criteria. The enter selection will then create a placeholder node for each element in the data array that does not exist. These nodes are created in relation to the parent element selection:
Conceptually, the enter selection’s placeholders are pointers to the parent element (documentation)
Just as we may select rectangles and enter circles, there is nothing linking the selector and the type/placement of the element we wish to enter.
In the example below, I select (and update) the SVG circle using d3.selectAll("svg circle")
, but I enter a div - and we can see that the div is appended to the element of the parent selection, which in the case of d3.selectAll()
is the document itself, not the SVG (and not the body), despite my selector:
var svg = d3.select("body").append("svg");
svg.append("circle")
.attr("cx",40)
.attr("cy",40)
.attr("r",20)
.attr("fill","orange");
var circles = d3.selectAll("svg circle")
.data(["steelblue","yellow"])
.attr("fill",function(d) { return d; });
circles.enter().append("div")
.html(function(d) { return d; });
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.10.0/d3.min.js"></script>
Ok, but why is D3 like that? Ultimately, I can't answer why, because this is the result a decision made by people other than myself. I'd hazard some of the reasoning may be along these lines:
If selectors such as (#bargraph rect)
determined where elements would be entered, then there could be certain challenges depending on the selector:
bargraph
- perhaps you want to enter elements elsewhere (but still needed to select only specific children of the element with id bargraph
).Granted the last two are about class rather than ID, but behavior should be similar regardless of the selector string (different behavior for IDs, classes, and elements in a selector string would likely be confusing). The chosen approach was probably chosen as the the cleanest and clearest for the programmer by those deciding.