在 Data joins 章节我们演示了当data和dom element个数相同时的情况
<div id="content">
<div></div>
<div></div>
<div></div>
</div>
给定下面的数据
var myData = [ 10, 40, 20 ];
当我们对div元素join上数据时
d3.select(‘#content‘)
.selectAll(‘div‘)
.data(myData);
myData
数据的个数和div元素的个数是相同的。
但是,如果数据和dom元素的个数如果不相同的话,会怎样呢?
- 如果数据个数多于dom元素个数那么说明有DOM元素的缺口,我们需要 add elements
- 如果数据个数少于dom元素个数,那么说明DOM元素有剩余,我们需要 remove elements
幸运的是D3可以帮助我们非常方便地管理这两种情况,分别通过 .enter来添加
而通过.exit来删除
.
.enter
.enter
指明了所有和data相比dom元素的缺口,enter和exit都是定义在update selection上的 (the selection returned by .data
):
d3.select(‘#content‘)
.selectAll(‘div‘)
.data(myData)
.enter();
.enter
返回代表需要增加的那些元素(已经绑定了数据)的selection. 通常跟随.enter的都是 .append
操作
d3.select(‘#content‘)
.selectAll(‘div‘)
.data(myData)
.enter()
.append(‘div‘);
Let’s look at an example. Suppose we have the following div
elements:
<div id="content">
<div></div>
<div></div>
<div></div>
</div>
and this data:
var myData = [‘A‘, ‘B‘, ‘C‘, ‘D‘, ‘E‘];
we use .enter
and .append
to add div
elements for D and E:
d3.select(‘#content‘)
.selectAll(‘div‘)
.data(myData)
.enter()
.append(‘div‘);
Note that we can join an array to an empty selection which is a very common pattern in the examples on the D3 website.
.exit
.exit
returns an exit selection which consists of the elements that need to be removed from the DOM. It’s usually followed by .remove
:
d3.select(‘#content‘)
.selectAll(‘div‘)
.data(myData)
.exit()
.remove();
Let’s repeat the example above, but using .exit
. Starting with elements:
<div id="content">
<div></div>
<div></div>
<div></div>
</div>
and data (notice that it’s shorter than the selection):
var myData = [‘A‘];
we use .exit
and .remove
to remove the surplus elements:
d3.select(‘#content‘)
.selectAll(‘div‘)
.data(myData)
.exit()
.remove();
Putting it all together
So far in this section we’ve not concerned ourselves with modifying elements using functions such as .style
, .attr
and .classed
.
D3 allows us to be specific about which elements are modified when new elements are entering. We can modify:
- the existing elements
- the entering elements
- both existing and entering elements
(Most of the time the last option is sufficient, but sometimes we might want to style entering elements differently.)
The existing elements are represented by the update selection. This is the selection returned by .data
and is assigned to u
in this example:
var myData = [‘A‘, ‘B‘, ‘C‘, ‘D‘, ‘E‘];
var u = d3.select(‘#content‘)
.selectAll(‘div‘)
.data(myData);
u.enter()
.append(‘div‘);
u.text(function(d) {
return d;
});
When the button is clicked, new elements are added, but because .text
is only called on the update selection, it’s only the existing elements that are modified. (Note that if the button is clicked a second time, all the elements are modified. This is because the selection will contain all 5 div
elements. Don’t worry about this too much if you’re new here!)
The entering elements are represented by the enter selection. This is the selection returned by .enter
. We can modify the enter selection using:
var myData = [‘A‘, ‘B‘, ‘C‘, ‘D‘, ‘E‘];
var u = d3.select(‘#content‘)
.selectAll(‘div‘)
.data(myData);
u.enter()
.append(‘div‘)
.text(function(d) {
return d;
});
When the button is clicked, new elements are added and their text content is updated. Only the entering elements have their text updated because we call .text
on the enter selection.
If we want to modify the existing and entering elements we could call .text
on the update and enter selections.
However D3 has a function .merge
which can merge selections together. This means we can do the following:
var myData = [‘A‘, ‘B‘, ‘C‘, ‘D‘, ‘E‘];
var u = d3.select(‘#content‘)
.selectAll(‘div‘)
.data(myData);
u.enter()
.append(‘div‘)
.merge(u)
.text(function(d) {
return d;
});
This is a big departure from v3. The entering elements were implicitly included in the update selection so there was no need for .merge
.
General update pattern
A common pattern (proposed by D3’s creator Mike Bostock) is to encapsulate the above behaviour of adding, removing and updating DOM elements in a single function:
function update(data) {
var u = d3.select(‘#content‘)
.selectAll(‘div‘)
.data(data);
u.enter()
.append(‘div‘)
.merge(u)
.text(function(d) {
return d;
});
u.exit().remove();
}
Typically the update function is called whenever the data changes.
Here’s another example where we colour entering elements orange:
function update(data) {
var u = d3.select(‘#content‘)
.selectAll(‘div‘)
.data(data);
u.enter()
.append(‘div‘)
.classed(‘new‘, true)
.text(function(d) {
return d;
});
u.text(function(d) {
return d;
})
.classed(‘new‘, false);
u.exit().remove();
}
Data join key function
When we do a data join D3 binds the first array element to the first element in the selection, the second array element to the second element in the selection and so on.
However, if the order of array elements changes (such as during element sorting, insertion or removal), the array elements might get joined to different DOM elements.
We can solve this problem by providing .data
with a key function. This function should return a unique id value for each array element, allowing D3 to make sure each array element stays joined to the same DOM element.
Let’s look at an example, first using a key function, and then without.
We start with an array [‘Z‘]
and each time the button is clicked a new letter is added at the start of the array.
Because of the key function each letter will stay bound to the same DOM element meaning that when a new letter is inserted each existing letter transitions into a new position:
Without a key function the DOM elements’ text is updated (rather than position) meaning we lose a meaningful transition effect:
There’s many instances when key functions are not required but if there’s any chance that your data elements can change position (e.g. through insertion or sorting) and you’re using transitions then you should probably use them.