D3 Key Function

I’m also new to d3 and was struggling with the key function. I didn’t find Tristan Reid’s answer illuminating, because it doesn’t really talk much about the key function.

Let’s work through an example, first without a key function, and then with.

Here’s our initial html before applying javascript. We’ve got two divs, and there is no data attached to anything.

<body>
    <div>** First div **</div>
    <div>** Second div **</div>
</body>

Calling data() with no key function

Let’s add a couple lines of javascript.

var arr1 = [35, 70, 24, 86, 59];
d3.select("body")
    .selectAll("div")
    .data(arr1)
    .enter()
    .append("div")
    .html(function(d) { return d });

What does our html look like now? Here is the html along with the values of the associated data (comments added).

<body>
    <div>** First div ** </div>   <!-- data:  35 -->
    <div>** Second div ** </div>  <!-- data:  70 -->
    <div>24</div>                 <!-- data:  24 -->
    <div>86</div>                 <!-- data:  86 -->
    <div>59</div>                 <!-- data:  59 -->
</body>

The data() call matched an array of divs with an array of values by use of a key. The default keys used for the arrays is the indexes. So these are the keys that were used.

selected divs (by text)  key       data elements  key
-----------------------  ---       -------------  ---
** First div **          0         35             0
** Second div **         1         70             1
                                   24             2
                                   86             3
                                   59             4

Going by the keys, two of the data elements have matches in the selected divs — those with keys 0 and 1. Those matching divs get bound to data, but nothing else happens them.

All the data elements without a matching key get passed to enter(). In this case, there is no match for the keys 2, 3, and 4. So those data elements get passed to enter(), and a new div is appended for each of them. The appended divs are also bound to their respective data values.

Calling data() with a key function

Let’s change our javascript, keeping what we have but adding a couple more lines. We’ll perform the same selects with a data call (with a different array), but this time using a key function. Notice the partial overlap between arr1 and arr2.

var arr1 = [35, 70, 24, 86, 59];
d3.select("body")
    .selectAll("div")
    .data(arr1)                            // no key function
    .enter()
    .append("div")
    .html(function(d) { return d });

var arr2 = [35, 7, 24, 2];
d3.select("body")
    .selectAll("div")
    .data(arr2, function(d) { return d })  // key function used
    .enter()
    .append("div")
    .html(function(d) { return "new: " + d});

The resulting html looks like this (with comment added):

<body>
    <div>** First div** </div>    <!-- data:  35 -->
    <div>** Second div ** </div>  <!-- data:  70 -->
    <div>24</div>                 <!-- data:  24 -->
    <div>86</div>                 <!-- data:  86 -->
    <div>59</div>                 <!-- data:  59 -->
    <div>new: 7</div>             <!-- data:  7 -->
    <div>new: 2</div>             <!-- data:  2 -->
</body>

The second call to data() used the value returned by the function for the keys. For the selected elements, the function returns a value derived from the data that had already been bound to them by the first call to data(). That is, their key is based on their bound data.

For the second data() call, the keys used for matching look like this.

selected divs (by text) key       data elements  key
----------------------- ---       -------------  ---
** First div **         35        35             35
** Second div **        70        7              7
24                      24        24             24
86                      86        2              2
59                      59

The data elements without matching keys are 7 and 2. Those data elements are passed to enter(). So we get two new divs appended to the body.

Okay, so now let’s look back at the original post. The OP said that there was no difference between the data() call with a function and without. That’s probably because — as Tristan Reid suggests — the key function was being used on html elements that had no bound data. When there’s no bound data, there will be no matching keys, so all of data elements will get passed to the enter() function.

Working through this example helped illuminate for me the relationships between selections, keys, and bound data. Hopefully it will be helpful to someone else.

Leave a Comment