I'm learning D3 and have come across an issue using the select operator.
Specifically, why does the following code add the <p>
element outside the body, instead of inside it?
var pData1 = d3.select("body").select("p").data([1]).enter().append("p");
I am using a totally blank HTML file with just <head>
and <body>
tags to test.
<html lang="en">
<head>
<title><!-- Insert your title here --></title>
<script type="text/javascript" src="d3.min.js"></script>
</head>
<body>
</body>
</html>
(This repeats the content from Lars Kotthoff's answer, but I'd spent time creating the demo so I thought I'd still post.)
The problem is that select
, unlike selectAll
, does not re-define the parent element for elements added in the enter()
selection.
d3.select("body").select("p#a")
.data([1])
.enter().append("p").attr("id", "a")
.text("This paragraph is appended to <html> (the document root)
because no selectAll statement reset the parent element.");
d3.selectAll("p#b")
.data([1])
.enter().append("p").attr("id", "b")
.text("This paragraph is appended to <html>
because the selectAll statement is called directly on the root.");
d3.selectAll("body").select("p#c")
.data([1])
.enter().append("p").attr("id", "c")
.text("This paragraph is also appended to <html>
because the last selectAll statement was called directly from the root.");
d3.select("body").selectAll("p#d")
.data([1])
.enter().append("p").attr("id", "d")
.text("This paragraph is appended to <body>
because the selectAll statement is a sub-selection of the body selection.");
d3.selectAll("body").selectAll("p#e")
.data([1])
.enter().append("p").attr("id", "e")
.text("This paragraph is also appended to <body>
because the final selectAll statement is a sub-selection of the body.");
http://fiddle.jshell.net/eLF4H/
It is unusual to use an enter chain after a select
statement (versus selectAll), because usually you are selecting multiple elements if you are going to do a data join. However, if you want to create the element if it doesn't exist or update it if it does, you have two options:
use a selectAll statement followed by the data join
var pdata1 = d3.select("body").selectAll("p#data")
//select element if it exists
.data([dataObject]);
//join to the current data
pdata1.enter().append("p").attr("id", "data");
//create element if required
pdata1.text(function(d){return d.textValue;});
//set or update the element based on the data
use an if statement to create the element if necessary and use .datum()
to bind the data
var pdata1 = d3.select("p#data")
//select element if it exists
if ( pdata1.empty() ) {
pdata1 = d3.select("body").append("p").attr("id", "data");
//create element if required
}
pdata1.datum(dataObject)
//note that you don't need to put the data into an array
.text(function(d){return d.textValue;});
//set or update the element based on the data
As suggested in the comments, the way to do what you're trying to do is to use .selectAll()
. The difference between .select()
and .selectAll()
is that .select()
returns elements while .selectAll()
returns elements grouped by ancestor. This then affects any subsequent .data()
operations in terms of what the elements are appended to. From the documentation:
Grouping by selectAll also affects subsequent entering placeholder nodes. Thus, to specify the parent node when appending entering nodes, use select followed by selectAll:
d3.select("body").selectAll("div")
there is no p element to select, so I would suggest appending the p element before entering your data:
var pData1 = d3.select("body").append("p")
pData1.data([1])...
do whatever else you need to do after .data, although there is no need to append another p
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