why is my d3 data outputting after body? - javascript

This is a simple page that demonstrates some basic functionality of d3. I made a dataset var dataset = [3,1,4,1,5]; and would like to output it as well as some paragraphs. The data is appearing, but after the body! Strange ...
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
"http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<title> demo project</title>
<script type="text/javascript" src="d3/d3.v2.js"></script>
</head>
<body>
<script type="text/javascript">
d3.select("body").append("p").text("hello!");
d3.select("p").append("text").text(" hello!!");
d3.select("body").append("p").text("hello2!");
d3.select("p:nth-child(3)").append("text").text(" hello2!!");
var dataset = [3,1,4,1,5];
d3.select("p:nth-child(3n+1)")
.data(dataset)
.enter()
.append("p")
.text(function(d) { return d; });
d3.select("p:nth-child(7n + 1)").append("text").text("hello againss?");
</script>
</body>
</html>
the page looks like this:
and the DOM looks like this (note the data shows up after the body close tag):
Also note that the line d3.select("p:nth-child(7n + 1)").append("text").text("hello againss?"); was intended to be printed after all my data, but it does not show up.

The short answer is that in your particular case the enter() selection's parentNode is the document (and not the body).
Let's take a simple example to see what an enter() selection looks like. Assuming we have a document with a body without any p elements.
var ps = d3.select("body").selectAll("p")
.data([0, 1, 2]);
Since no p elements existed yet, the enter() selection will have three elements. Let's inspect the enter selection:
You see that the inner array has a property named parentNode. When you add new elements using selection.append() or selection.insert() the new elements will be created as children of that parentNode.
So, inspecting ps.enter()[0].parentNode will reveal the body element. It now becomes clear that, in a data join, the selection before the selectAll specifies the parentNode; in the above case that was d3.select("body").
What if we had omitted the select("body") part in the data join?
// example of bad data join
var ps2 = d3.selectAll("p")
.data([0, 1, 2]);
It turns out that in this case ps2.enter()[0].parentNode is the #document! That means that if you add elements using this enter() selection, they will become the document's direct children. The append method will add them to the end of the document; i.e. after the body.
The last case is basically what you've encountered. Your data join and enter expression is not correct; it should follow this pattern:
d3.select(parent).selectAll(element)
.data(data)
.enter().append(element);
BTW, there is no HTML text element. So, append("text") doesn't seem meaningful.

Related

Do I need to have a parameter for d3.js selectAll() function for a data join? [duplicate]

I've seen some D3 codes with a pattern like this for appending elements:
var circles = svg.selectAll(null)
.data(data)
.enter()
.append("circle");
I really don't get this snippet. Why selecting null?
The way I understand D3, if one is appending circles, it should be:
var circles = svg.selectAll("circle")
.data(data)
.enter()
.append("circle");
The same way, if one is appending HTML paragraphs it should be:
var circles = svg.selectAll("p")
.data(data)
.enter()
.append("p");
The same goes for classes: if one is appending elements with a class foo, it should be selectAll(".foo").
However, selectAll(null) does work! The elements get appended.
So, what's the meaning of that null? What am I missing here?
Note: this is a self-answered question, trying to provide a "canonical" Q&A on a subject that has been touched on by many previous questions and not explained by the API. Most of the answer below is from an example I wrote in the extinct StackOverflow Documentation.
tl;dr
The objective of using selectAll(null) is to guarantee that the "enter" selection always corresponds to the elements in the data array, containing one element for every element in the data.
The "enter" selection
To answer your question, we have to briefly explain what is an "enter" selection in D3.js. As you probably know, one of the main features of D3 is the ability of binding data to DOM elements.
In D3.js, when one binds data to DOM elements, three situations are possible:
The number of elements and the number of data points are the same;
There are more elements than data points;
There are more data points than elements;
In the situation #3, all the data points without a corresponding DOM element belong to the "enter" selection.
Thus, In D3.js, "enter" selections are selections that, after joining elements to the data, contains all the data that don't match any DOM element. If we use an append function in an "enter" selection, D3 will create new elements, binding that data for us.
This is a Venn diagram explaining the possible situations regarding number of data points/number of DOM elements:
Binding data to already existing DOM elements
Let's break your proposed snippet for appending circles.
This...
var circles = svg.selectAll("circle")
.data(data)
... binds the data to a selection containing all circles. In D3 lingo, that's the "update" selection.
Then, this...
.enter()
.append("circle");
... represents the "enter" selection, creating a circle for each data point that doesn't match a selected element.
Sure, when there is no element (or a given class) in the selection, using that element (or that class) in the selectAll method will work as intended. So, in your snippet, if there is no <circle> element in the svg selection, selectAll("circle") can be used to append a circle for each data point in the data array.
Here is a simple example. There is no <p> in the <body>, and our "enter" selection will contain all the elements in the data array:
var body = d3.select("body");
var data = ["red", "blue", "green"];
var p = body.selectAll("p")
.data(data)
.enter()
.append("p")
.text(d=> "I am a " + d + " paragraph!")
.style("color", String)
<script src="https://d3js.org/d3.v4.min.js"></script>
But what happens if we already have a paragraph in that page? Let's have a look:
var body = d3.select("body");
var data = ["red", "blue", "green"];
var p = body.selectAll("p")
.data(data)
.enter()
.append("p")
.text(d=> "I am a " + d + " paragraph!")
.style("color", String)
<script src="https://d3js.org/d3.v4.min.js"></script>
<p>Look Ma, I'm a paragraph!</p>
The result is clear: the red paragraph disappeared! Where is it?
The first data element, "red", was bound to the already existing paragraph. Then, just two paragraphs were created (our "enter" selection), the blue one and the green one.
That happened because, when we used selectAll("p"), we selected, well, <p> elements! And there was already one <p> element in that page.
Selecting null
However, if we use selectAll(null), nothing will be selected! It doesn't matter that there is already a paragraph in that page, our "enter" selection will always have all the elements in the data array.
Let's see it working:
var body = d3.select("body");
var data = ["red", "blue", "green"];
var p = body.selectAll(null)
.data(data)
.enter()
.append("p")
.text(d=> "I am a " + d + " paragraph!")
.style("color", String)
<script src="https://d3js.org/d3.v4.min.js"></script>
<p>Look Ma, I'm a paragraph!</p>
And that's the purpose of selecting null: we guarantee that there is no match between the selected elements and the data array.
Selecting null and performance
Since we are not selecting anything, selectAll(null) is by far the fastest way to append new elements: we don't have to traverse the DOM searching for anything.
Here is a comparison, using jsPerf:
https://jsperf.com/selecting-null/1
In this very simple scenario, selectAll(null) was substantially faster. In a real page, full of DOM elements, the difference may be even bigger.
When NOT to use selectAll(null)
As we just explained, selectAll(null) won't match any existing DOM element. It's a nice pattern for a fast code that always append all the elements in the data array.
However, if you plan to update your elements, that is, if you plan to have an "update" (and an "exit") selection, do not use selectAll(null). In that case, select the element (or the class) you plan to update.
So, if you want to update circles according to a changing data array, you would do something like this:
//this is the "update" selection
var circles = svg.selectAll("circle")
.data(data);
//this is the "enter" selection
circles.enter()
.append("circle")
.attr("foo", ...
//this is the "exit" selection
circles.exit().remove();
//updating the elements
circles.attr("foo", ...
In that case, if you use selectAll(null), the circles will be constantly appended to the selection, piling up, and no circle will be removed or updated.
PS: Just as a historical curiosity, the creation of the selectAll(null) pattern can be traced back to these comments by Mike Bostock and others: https://github.com/d3/d3-selection/issues/79

First data is ignored without using selectAll in d3.js

Snippet 1
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>D3 Test</title>
<script type="text/javascript" src="http://d3js.org/d3.v3.js"></script>
</head>
<body>
<script type="text/javascript">
var data = [];
for (i = 0; i < 3; i += 1) {
data.push(i);
}
d3.select('body')
// .selectAll('p')
.data(data)
.enter()
.append('p')
.text(function(d) {
return d;
});
</script>
</body>
</html>
Snippet 2
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>D3 Test</title>
<script type="text/javascript" src="http://d3js.org/d3.v3.js"></script>
</head>
<body>
<script type="text/javascript">
var data = [];
for (i = 0; i < 3; i += 1) {
data.push(i);
}
d3.select('body')
.selectAll('p')
.data(data)
.enter()
.append('p')
.text(function(d) {
return d;
});
</script>
</body>
</html>
The code .selectAll('p') is commented in the first snippet. I could not figure why the first data is ignored due to that reason.
I am a newbie to d3.js and what I understood ,as I don't have a p tag in my HTML the .selectAll('p') returns an empty selection and p are appended as per my data count.
Thanks in advance.
To understand what happened, you have to understand what is an "enter" selection. The example at the bottom of this post briefly explains both the "enter" selection and why you have to selectAll("p") in your second snippet, even if you don't have any <p> in the document.
Now, let's see your snippets:
Your first snippet:
In your first snippet, data is [0, 1, 2]. So, you have 3 elements. When you select the body, there is clearly a body in the DOM. So, you associate the first datum (0) to this element (body). Now, you have 2 data not associated to any DOM element: 1 and 2. These two data are your "enter" selection. When you append the <p>, your enter selection has only those two numbers.
Your second snippet
In your second snippet, data is again [0, 1, 2]. The difference is that now you select all <p>... but there is none. This is the "placeholder" in the example I linked. As there is no <p> in the DOM to associate with the data, your enter selection has all 3 data: 0, 1 and 2.
As I wrote in the example:
If in your "enter" selection you select something that doesn't exist, your "enter" selection will always contain all your data.
The role of placeholders in "enter" selections
What is an enter selection?
In D3.js, when one binds data to DOM elements, three situations are possible:
The number of elements and the number of data points are the same;
There are more elements than data points;
There are more data points than elements;
In the situation #3, all the data points without a corresponding DOM element belong to the enter selection. Thus, In D3.js, enter selections are selections that, after joining elements to the data, contains all the data that don't match any DOM element. If we use an append function in an enter selection, D3 will create new elements binding that data for us.
This is a Venn diagram explaining the possible situations regarding number of data points/number of DOM elements:
As we can see, the enter selection is the blue area at the left: data points without corresponding DOM elements.
The structure of the enter selection
Typically, an enter selection has these 4 steps:
selectAll: Select elements in the DOM;
data: Counts and parses the data;
enter: Comparing the selection with the data, creates new elements;
append: Append the actual elements in the DOM;
This is a very basic example (look at the 4 steps in the var divs):
var data = [40, 80, 150, 160, 230, 260];
var body = d3.select("body");
var divs = body.selectAll("div")
.data(data)
.enter()
.append("div");
divs.style("width", function(d) { return d + "px"; })
.attr("class", "divchart")
.text(function(d) { return d; });
And this is the result (jsfiddle here):
Notice that, in this case, we used selectAll("div") as the first line in our "enter" selection variable. We have a dataset with 6 values, and D3 created 6 divs for us.
The role of placeholders
But suppose that we already have a div in our document, something like <div>This is my chart</div> at the top. In that case, when we write:
body.selectAll("div")
we are selecting that existent div. So, our enter selection will have only 5 datum without matching elements. For instance, in this jsfiddle, where there is already a div in the HTML ("This is my chart"), this will be the outcome:
We don't see the value "40" anymore: our first "bar" disappeared, and the reason for that is that our "enter" selection now has only 5 elements.
What we have to understand here is that in the first line of our enter selection variable, selectAll("div"), those divs are just placeholders. We don't have to select all the divs if we are appending divs, or all the circle if we are appending circle. We can select different things. And, if we don't plan to have an "update" or an "exit" selection, we can select anything:
var divs = body.selectAll(".foo")//this class doesn't exist, and never will!
.data(data)
.enter()
.append("div");
Doing this way, we are selecting all the ".foo". Here, "foo" is a class that not only doesn't exist, but also it's never created anywhere else in the code! But it doesn't matter, this is only a placeholder. The logic is this:
If in your "enter" selection you select something that doesn't exist, your "enter" selection will always contain all your data.
Now, selecting .foo, our "enter" selection have 6 elements, even if we already have a div in the document:
And here is the corresponding jsfiddle.
Selecting null
By far, the best way to guarantee that you are selecting nothing is selecting null. Not only that, but this alternative is way faster than any other.
Thus, for an enter selection, just do:
selection.selectAll(null)
.data(data)
.enter()
.append(element);
Here is a demo fiddle: https://jsfiddle.net/gerardofurtado/th6s160p/
Conclusion
When dealing with "enter" selections, take extra care to do not select something that already exists. You can use anything in your selectAll, even things that don't exist and will never exist (if you don't plan to have an "update" or an "exit" selection).
The code in the examples is based on this code by Mike Bostock: https://bl.ocks.org/mbostock/7322386

jQuery "add" Only Evaluated When "appendTo" Called

this has been driving me crazy since yesterday afternoon. I am trying to concatenate two bodies of selected HTML using jQuery's "add" method. I am obviously missing something fundamental. Here's some sample code that illustrated the problem:
<html>
<head>
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js"></script>
</head>
<body>
<p id="para1">This is a test.</p>
<p id="para2">This is also a test.</p>
<script>
var para1 = $("#para1").clone();
var para2 = $("#para2").clone();
var para3 = para1.add(para2);
alert("Joined para: " + para3.html());
para3.appendTo('body');
</script>
</body>
</html>
I need to do some more manipulation to "para3" before the append, but the alert above displays only the contents of "para1." However, the "appendTo appends the correct, "added" content of para1 and para2 (which subsequently appears on the page).
Any ideas what's going on here?
As per the $.add,
Create a new jQuery object with elements added to the set of matched elements.
Thus, after the add, $para3 represents a jQuery result set of two elements ~> [$para1, $para2]. Then, per $.html,
Get the HTML contents of the first element in the set of matched elements or set the HTML contents of every matched element.
So the HTML content of the first item in the jQuery result ($para1) is returned and subsequent elements (including $para2) are ignored. This behavior is consistent across jQuery "value reading" functions.
Reading $.appendTo will explain how it works differently from $.html.
A simple map and array-concat can be used to get the HTML of "all items in the result set":
$.map($para3, function (e) { return $(e).html() }).join("")
Array.prototype.map.call($para3, function (e) { return $(e).html() }).join("")
Or in this case, just:
$para1.html() + $para2.html()
Another approach would be to get the inner HTML of a parent Element, after the children have been added.

Difference between a selected node and its imported node

In the below code, I commented A and B at each lines.
What is the different between A and B? And which is recommended?
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
</head>
<template id="templatetest">
<div>test</div>
</template>
<body></body>
<script type="text/javascript">
var t = document.querySelector('#templatetest');
var n = document.importNode(t.content, true);
document.body.appendChild(n); //A
document.body.appendChild(t.content); //B
</script>
</html>
And one more question..
The above code renders two "test"s. But when I switch positions of A and B like the below code, only one "test" is rendered somehow. I would like to know why this happens.
<script type="text/javascript">
var t = document.querySelector('#templatetest');
document.body.appendChild(t.content); //B
var n = document.importNode(t.content, true);
document.body.appendChild(n); //A
</script>
The points is importNode creates a copy of the node and returns it, then you can add it in a new parent using appendChild. It's intended to be used when you're getting nodes from anoother documents and inserting it in your doc (for example, when you're moving nodes between different XML files), this function will fix things like the ownerDocument property and other internal things I believe.
The appendChild just to append nodes to other nodes, if the node you is appending is already a child of any other node, first it will remove it from there and move it to its new parent.
In your first example:
A - you create a copy of t.content (n - importNode) and append it to the body;
B - you just append t.content to body, thus removing it from it's original parent
Your second example
B - You moves the content of #templatetest to the body
A - #templatetest is now empty, it has no more content, so there is nothing to you copy and append in your body anymore

After cloning an element find the original element in the document

I clone my mainSection like this (I have to clone it because, there are new elements added to #main over AJAX, and I don't want to search through them):
$mainSection = $('#main').clone(true);
then i search through the cloned main section for an element:
var searchTermHtml = 'test';
$foundElement = $mainSection.filter(":contains('"+searchTermHtml+"')");
When I find the string 'test' in the #mainSection I want to get the original element from it in the $mainSection so I can scroll to it via:
var stop = $foundElementOriginal.offset().top;
window.scrollTo(0, stop);
The question is: how do I get the $foundElementOriginal?
Since you're changing the content of #main after cloning it, using structural things (where child elements are within their parents and such) won't be reliable.
You'll need to put markers of some kind on the elements in #main before cloning it, so you can use those markers later to relate the cloned elements you've found back to the original elements in #main. You could mark all elements by adding a data-* attribute to them, but with greater knowledge of the actual problem domain, I expect you can avoid being quite that profligate.
Here's a complete example: Live Copy
<!DOCTYPE html>
<html>
<head>
<script src="http://code.jquery.com/jquery-1.11.0.min.js"></script>
<meta charset="utf-8">
<title>Example</title>
</head>
<body>
<div id="main">
<p>This is the main section</p>
<p>It has three paragraphs in it</p>
<p>We'll find the one with the number word in the previous paragraph after cloning and highlight that paragraph.</p>
</div>
<script>
(function() {
"use strict";
// Mark all elements within `#main` -- again, this may be
// overkill, better knowledge of the problem domain should
// let you narrow this down
$("#main *").each(function(index) {
this.setAttribute("data-original-position", String(index));
});
// Clone it -- be sure not to append this to the DOM
// anywhere, since it still has the `id` on it (and
// `id` values have to be unique within the DOM)
var $mainSection = $("#main").clone(true);
// Now add something to the real main
$("#main").prepend("<p>I'm a new first paragraph, I also have the word 'three' but I won't be found</p>");
// Find the paragraph with "three" in it, get its original
// position
var originalPos = $mainSection.find("*:contains(three)").attr("data-original-position");
// Now highlight it in the real #main
$("#main *[data-original-position=" + originalPos + "]").css("background-color", "yellow");
})();
</script>
</body>
</html>

Categories

Resources