Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does this D3 code add the <p> element outside the body, instead of inside it?

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>
like image 323
Takeshi Patterson Avatar asked May 04 '14 18:05

Takeshi Patterson


3 Answers

(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
    
like image 55
AmeliaBR Avatar answered Oct 18 '22 08:10

AmeliaBR


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")

like image 3
Lars Kotthoff Avatar answered Oct 18 '22 09:10

Lars Kotthoff


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

like image 2
rysloan Avatar answered Oct 18 '22 07:10

rysloan