首页 > 代码库 > d3js enter/exit深入了解

d3js enter/exit深入了解

在 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‘);
View source | Edit in JS Bin

Note that we can join an array to an empty selection which is a very common pattern in the examples on the D3 website.

View source | Edit in JS Bin

.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();
View source | Edit in JS Bin

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;
});
View source | Edit in JS Bin

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;
  });
View source | Edit in JS Bin

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;
  });
View source | Edit in JS Bin
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();
}
View source | Edit in JS Bin

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();
}
View source | Edit in JS Bin

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.

d3js enter/exit深入了解