In D3.js, D3 stands for Data-Driven Document. It means that charts can be created from data. This concept is the key of D3.js, and the main feature is data binding.
Let's take a simple illustration, assume you have a set of data to display in a bar chart. You can create a loop to iterate over your data and display each bar. D3.js offers a better option: data binding. With this feature, you can associate your set of data to you chart in a more convinient way.
Let's start with a very simple example (from Scrimba):
// Set of data
var dataset = ['A', 'B', 'C', 'D', 'E'];
// For each data from dataset, create a new section
d3.select('body')
.selectAll('p')
.data(dataset)
.enter()
.append('p')
.text('Paragraph');
You can see that, without loop, the previous example displays 5 paragraphs:
What may seems weird here is that the selection is done before
appending the paragraphs. selectAll('p')
is called before append('p')
.
Let's detail the source code.
If you still don't understand this section, don't worry! Just accept the idea that D3.js will create new elements according to the size of the dataset.
First, we select the body (for later appending the paragrahs). Then we select all the
existing paragraphs. Since there no existing paragraphs, selectAll('p')
returns a
new empty selection. Here is the object returned:
_groups: [NodeList(0)]
_parents: [body]
__proto__: Object
When calling data(dataset)
, the previous empty selection is joined to a 5 elements array.
.data(dataset)
returns 5 empty new selection:
_groups: Array(1)
0: (5) [empty × 5]
length: 1
__proto__: Array(0)
_parents: [body]
_enter: [Array(5)]
_exit: [Array(0)]
__proto__: Object
enter()
identifies any DOM elements that needs to be added when the joined array
is longer than the selection. That the case here, since our selections are empty.
_groups: Array(1)
0: (5) [rt, rt, rt, rt, rt]
length: 1
__proto__: Array(0)
_parents: [body]
__proto__: Object
When calling the append('p')
, the <p>
tag is appended to the 5 pending
element:
_groups: Array(1)
0: (5) [p, p, p, p, p]
length: 1
__proto__: Array(0)
_parents: [body]
__proto__: Object
You can see here that 5 <p>
elements are created in the DOM.
If it's stil not clear, you can find a more detailed explanation here: Thinking with Joins.
Five new elements are created, but our dataset is not linked to our elements.
Fortunately, the .text()
function can be called with an anomymous function
as parameter. Here is the syntax to link our dataset to our <p>
tags.
// Set of data
var dataset = ['A', 'B', 'C', 'D', 'E'];
// For each data from dataset, create a new section
d3.select('body')
.selectAll('p')
.data(dataset)
.enter()
.append('p')
.text(function(id) { return 'Section ' + id; });
This is how data are linked with new elements:
The join only exists when the data (dataset)
functions are called.
Do not imagine that the charts will update automatically when the data changes.
We will have to associate the data again with the update ()
function this time.
Anonymous function can be written as arrow functions. This syntax:
.text(function(id) { return 'Section ' + id; });
is equivalent to:
.text((id) => 'Section ' + id; );
In the following, I'll use arrow functions syntax.
The more you'll practice, the more you'll understand why this mecanism is more convinient than iterating over datasets.