Data Binding in D3
In this chapter, you will learn how to bind data to DOM elements and create new elements based on your data.
D3 includes the following important methods for data binding.
Method | Description |
---|---|
data() | Joins data to the selected elements |
enter() | Creates a selection with placeholder references for missing elements |
exit() | Removes nodes and adds them to the exit selection which can be later removed from the DOM |
datum() | Injects data to the selected element without computing a join. |
data()
D3 is data driven. The data()
function is used to join the specified array of data to the selected DOM elements and return the updated selection. D3 works with different types of data like Array, CSV, TSV, JSON, XML etc.
You can pass two types of value to the data()
function, an array of values (number or object) or a function of data.
The following example demonstrates how to join data as an array into your existing DOM element using the data()
function.
<p>D3 Tutorials</p>
<script>
var myData = ["Hello World!"];
var p = d3.select("body")
.selectAll("p")
.data(myData)
.text(function (d) {
return d;
});
</script>
In the above example, we have a paragraph element <p>D3 Tutorials</p>
. We have created a data array named 'myData' with a single string "Hello World" that we want to bind to the <p>
element.
This is how it works:
d3.select("body")
selects the HTML Body element.
.selectAll("p")
returns the paragraph element. Since there is only one paragraph element, this will return a selection with one<p>
element object.
.data(myData)
- the data()
function then binds our data array 'myData' to the selection returned from the previous selection. Since our selection has single<p>
element, the data()
function will bind the first value from our data array to the <p>
element.
In the .text(function(d, i) { return d; });
, the text()
function adds the data as text to each of our selection element. Each array value is passed as the first argument (d) to text function. In this case, an existing text from paragraph element, 'D3 tutorial', will get replaced with the first array value 'Hello World'.
Remember, you need to pass an array to the data()
function. It does not do anything if you provide a constant value.
<p> </p>
<script>
var myData = 100;
var p = d3.select("body")
.selectAll("p")
.data(myData)
.text(function (d, i) {
return d;
});
</script>
The above example will not display anything as data() function requires an array.
The following example demonstrates joining data to multiple elements.
<p> </p>
<p> </p>
<p> </p>
<script>
var myData = ["Hello World!", "Hello D3","Hello JavaScript"];
var p = d3.select("body")
.selectAll("p")
.data(myData)
.text(function (d, i) {
return d;
});
</script>
The above example would display the following result in the browser.
In the above example, HTML includes three <p>
elements and data array myData also includes three values. So, data() joins three data values to three selected<p>
elements respectively and text() function displays this data value as a text. Let's take a look at another example.
<body>
<p>D3 Tutorials </p>
<script>
var myData = [1, 2, 3, 4, 5];
var p = d3.select("body")
.selectAll("p")
.data(myData)
.text(function (d, i) {
return d;
});
</script>
</body>
In the above example, we have replaced our data array with [1, 2, 3, 4, 5]. Just like in the previous example, we select all the paragraph elements from the body. We then bind our data to the selection and add the data as text to our paragraph selection. Notice how the text 'D3 tutorial' got replaced with the first element from our data array <p>1</p>
.
But what happened to the rest of the data elements? When we selected our paragraph elements using .selectAll("p")
, it returned only one element in the selection because we had only one paragraph element in the DOM. Hence, when we bind the data to our paragraph selection, only the first data element bound to the one available paragraph element. Rest of the data elements from the array were ignored because there were no other <p>
elements.
To handle such scenarios, where we are not aware of the number of data elements in our dataset and DOM elements, d3 provides the enter() function.
Let's see how we can use the enter() function to handle unknown data.
enter()
In the above example, we have seen that the data()
function joins one on one mapping to the selection elements (nodes) and array values. But, what if number of elements (nodes) and data values are not matching? In such cases, we cannot get our selection using only select() and selectAll() because they may either return lesser elements than your dataset, or no selections at all, if the html code is not in place.
The enter()
method dynamically creates placeholder references corresponding to the number of data values. The output of enter()
can be fed to append()
method and append()
will create DOM elements for which there are no corresponding DOM elements on the page.
What happens if we don't use enter()? D3 will simply update the existing nodes like we saw in the earlier example. If there are no existing nodes available to bind the data to, there won't be any updates.
In the following example, there are 6 data values in our array. So enter() will create six reference placeholders and append will create six span elements.
<body>
<script>
var data = [4, 1, 6, 2, 8, 9];
var body = d3.select("body")
.selectAll("span")
.data(data)
.enter()
.append("span")
.text(function(d) { return d + " "; });
</script>
</body>
Just like our previous example, we have taken a data array [4, 1, 6, 2, 8, 9].
Let's see what our program does:
d3.select("body")
This statement selects the HTML Body.
.selectAll("span")
At this point, there are no span elements in the body. So this will return an empty array.
.data(data)
We provide our data array to the data() function. Since our array has six elements, the code after this will run six times for every element in the array.
.enter()
The enter()
function checks for <span>
elements corresponding to our five array elements. Since it does not find any, it will create a span for each of the five elements.
.append("span")
And this will append above created spans to the body element.
.text(function(d) { return d + " "; });
And finally our text function adds each of the numbers from the data array as text to each of our span selections and prints them onto the screen!
Let's take the program to the next level and add logic to paint the numbers green if the numbers are even and red if the numbers are odd.
<body>
<script>
var data = [4, 1, 6, 2, 8, 9];
var body = d3.select("body")
.selectAll("span")
.data(data)
.enter().append("span")
.style('color', function(d) {
if (d % 2 === 0) {
return "green";
} else {
return "red";
}
})
.text(function(d) { return d + " "; });
</script>
</body>
What we added here was the style() function with our logic in it:
.style('color', function(d) {
<code>.style('color', function(d) {
if (d % 2 === 0) {
return "green";
} else {
return "red";
}
});
Here, we are styling our elements based on our logic. For each data element, the logic runs to determine whether the element is even or odd and returns a color based on the result.
As mentioned in the data() section, you can specify a function instead of an array to the data() function if the previous selection returns multiple groups (using multiple select methods). This function will be executed for each group in the selection. Consider the following example of a matrix with two dimensional array.
<body>
<script>
var matrix = [
[1, 2, 3, 4],
[5, 6, 7, 8],
[9, 10, 11, 12],
[13, 14, 15, 16]
];
var tr = d3.select("body")
.append("table") // adds <table>
.selectAll("tr") // selects all <tr>
.data(matrix) // joins matrix array
.enter() // create placeholders for each row in the array
.append("tr");// create <tr> in each placeholder
var td = tr.selectAll("td")
.data(function (d) { // joins inner array of each row
console.log(d);
return d;
})
.enter() // create placeholders for each element in an inner array
.append("td") // creates <td> in each placeholder
.text(function (d) {
console.log(d);
return d; // add value of each inner array as a text in <td>
});
</script>
</body>
As you can see, we have specified a function as a parameter in the data() after selection of td
. The tr.selectAll("td")
returns multiple td
for the selection of each tr
and thus it forms multiple <tr><td></td></tr>
groups. As you can see in the result, console.log(d)
returns 1st element in the two dimensional array that is a row in an array. The parameter d in the text(function(d))
represents a single element of a row returned from the previous data() function.
This was a basic example of D3 to show how D3 handles data and what you can do with the data. In the future chapters, we will be using these functions extensively as we see more examples to understand D3 in depth.
exit()
While enter() is used to add new reference nodes, exit is used to remove a node.
In the below code, all p elements will be removed. With exit(), the elements enter an exit phase. This means that all the exited elements are stored in some invisible place ready to be removed when the command is given. And that command is remove(). remove() will remove all the 'exited' nodes from the DOM.
<body>
<p>D3 Tutorials</p>
<p></p>
<p></p>
<script>
var myData = ["Hello World!"];
var p = d3.select("body")
.selectAll("p")
.data(myData)
.text(function (d, i) {
return d;
})
.exit()
.remove();
</script>
</body>
In the above example, HTML included three <p> elements whereas data array includes only one data value. So, the .exit().remove() removed additional<p>
elements.
datum()
The datum() function is used for static visualization which does not need updates. It binds data directly to an element.
<body>
<p>D3 Tutorials</p>
<script>
d3.select("body")
.select("p")
.datum(100)
.text(function (d, i) {
return d;
});
</script>
</body>
Visit Joining Data section of D3 documentation to learn more about data joining.
In the next chapter, learn how to load different types of data from files.