Skip to content

Commit

Permalink
Fix for null node in selection.data.
Browse files Browse the repository at this point in the history
If a selection is missing elements, then selection.data would still attempt to
evaluate the optional key function for the missing elements, resulting in an
uncaught TypeError. For example:

  d3.selectAll("p")
    .select(function(d, i) { return i & 1 ? this : null; })
      .data([1, 2, 3], Number);

Now, any missing elements are automatically assigned to the exit selection.
  • Loading branch information
mbostock committed Oct 21, 2015
1 parent 5b981a1 commit 7eaf442
Show file tree
Hide file tree
Showing 2 changed files with 42 additions and 6 deletions.
14 changes: 8 additions & 6 deletions src/selection/data.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,12 +36,14 @@ d3_selectionPrototype.data = function(value, key) {
keyValue;

for (i = -1; ++i < n;) {
if (nodeByKeyValue.has(keyValue = key.call(node = group[i], node.__data__, i))) {
exitNodes[i] = node; // duplicate selection key
} else {
nodeByKeyValue.set(keyValue, node);
if (node = group[i]) {
if (nodeByKeyValue.has(keyValue = key.call(node, node.__data__, i))) {
exitNodes[i] = node; // duplicate selection key
} else {
nodeByKeyValue.set(keyValue, node);
}
keyValues[i] = keyValue;
}
keyValues[i] = keyValue;
}

for (i = -1; ++i < m;) {
Expand All @@ -55,7 +57,7 @@ d3_selectionPrototype.data = function(value, key) {
}

for (i = -1; ++i < n;) {
if (nodeByKeyValue.get(keyValues[i]) !== true) {
if (i in keyValues && nodeByKeyValue.get(keyValues[i]) !== true) {
exitNodes[i] = group[i];
}
}
Expand Down
34 changes: 34 additions & 0 deletions test/selection/data-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,40 @@ suite.addBatch({
assert.domEqual(exit[1][0], span[1][0]);
assert.domNull(exit[1][1]);
},
"does not evaluate the key function on null nodes": function(span) {
var node = span[0][0];
span[0][0] = null;

var update = span.data([1, 2], Number);
assert.isFalse(update.empty());
assert.equal(update.length, 2);
assert.equal(update[0].length, 2);
assert.equal(update[1].length, 2);
assert.domEqual(update[0][0], span[0][1]);
assert.domNull(update[0][1]);
assert.domEqual(update[1][0], span[1][1]);
assert.domNull(update[1][1]);

var enter = update.enter();
assert.equal(enter.length, 2);
assert.equal(enter[0].length, 2);
assert.equal(enter[1].length, 2);
assert.domNull(enter[0][0]);
assert.deepEqual(enter[0][1], {__data__: 2});
assert.domNull(enter[1][0]);
assert.deepEqual(enter[1][1], {__data__: 2});

var exit = update.exit();
assert.equal(exit.length, 2);
assert.equal(exit[0].length, 2);
assert.equal(exit[1].length, 2);
assert.domNull(exit[0][0]);
assert.domNull(exit[0][1]);
assert.domEqual(exit[1][0], span[1][0]);
assert.domNull(exit[1][1]);

span[0][0] = node;
},
"handles keys that are in the default object's prototype chain": function(span) {
// This also applies to the non-standard "watch" and "unwatch" in Mozilla Firefox.
var update = span.data(["hasOwnProperty", "isPrototypeOf", "toLocaleString", "toString", "valueOf"], String);
Expand Down

0 comments on commit 7eaf442

Please sign in to comment.