Skip to content

Commit cd1dd5b

Browse files
committed
Copy-on-write for selection.data.
Also fix a bug where the exit groups could contain undefined entries, rather than being sparse, when a key function is used to join data.
1 parent 39a60e1 commit cd1dd5b

File tree

3 files changed

+123
-76
lines changed

3 files changed

+123
-76
lines changed

src/selection/arrayify.js

-12
This file was deleted.

src/selection/data.js

+47-63
Original file line numberDiff line numberDiff line change
@@ -1,79 +1,56 @@
1-
import arrayify from "./arrayify";
1+
import {Selection} from "./index";
22
import constant from "../constant";
33

44
var keyPrefix = "$"; // Protect against keys like “__proto__”.
55

6-
function bindIndex(parent, update, enter, exit, data) {
6+
function bindIndex(parent, group, enter, update, exit, data) {
77
var i = 0,
88
node,
9-
nodeLength = update.length,
10-
dataLength = data.length,
11-
minLength = Math.min(nodeLength, dataLength);
12-
13-
// Clear the enter and exit arrays, and then initialize to the new length.
14-
enter.length = 0, enter.length = dataLength;
15-
exit.length = 0, exit.length = nodeLength;
9+
groupLength = group.length,
10+
dataLength = data.length;
1611

17-
for (; i < minLength; ++i) {
18-
if (node = update[i]) {
12+
// Put any non-null nodes that fit into update.
13+
// Put any null nodes into enter.
14+
// Put any remaining data into enter.
15+
for (; i < dataLength; ++i) {
16+
if (node = group[i]) {
1917
node.__data__ = data[i];
18+
update[i] = node;
2019
} else {
2120
enter[i] = new EnterNode(parent, data[i]);
2221
}
2322
}
2423

25-
// Note: we don’t need to delete update[i] here because this loop only
26-
// runs when the data length is greater than the node length.
27-
for (; i < dataLength; ++i) {
28-
enter[i] = new EnterNode(parent, data[i]);
29-
}
30-
31-
// Note: and, we don’t need to delete update[i] here because immediately
32-
// following this loop we set the update length to data length.
33-
for (; i < nodeLength; ++i) {
34-
if (node = update[i]) {
35-
exit[i] = update[i];
24+
// Put any non-null nodes that don’t fit into exit.
25+
for (; i < groupLength; ++i) {
26+
if (node = group[i]) {
27+
exit[i] = node;
3628
}
3729
}
38-
39-
update.length = dataLength;
4030
}
4131

42-
function bindKey(parent, update, enter, exit, data, key) {
32+
function bindKey(parent, group, enter, update, exit, data, key) {
4333
var i,
4434
node,
45-
dataLength = data.length,
46-
nodeLength = update.length,
4735
nodeByKeyValue = {},
48-
keyValues = new Array(nodeLength),
36+
groupLength = group.length,
37+
dataLength = data.length,
38+
keyValues = new Array(groupLength),
4939
keyValue;
5040

51-
// Clear the enter and exit arrays, and then initialize to the new length.
52-
enter.length = 0, enter.length = dataLength;
53-
exit.length = 0, exit.length = nodeLength;
54-
55-
// Compute the keys for each node.
56-
for (i = 0; i < nodeLength; ++i) {
57-
if (node = update[i]) {
58-
keyValues[i] = keyValue = keyPrefix + key.call(node, node.__data__, i, update);
59-
60-
// Is this a duplicate of a key we’ve previously seen?
61-
// If so, this node is moved to the exit selection.
62-
if (nodeByKeyValue[keyValue]) {
63-
exit[i] = node;
64-
}
65-
66-
// Otherwise, record the mapping from key to node.
67-
else {
41+
// Compute the key for each node.
42+
// If multiple nodes have the same key, only the first one counts.
43+
for (i = 0; i < groupLength; ++i) {
44+
if (node = group[i]) {
45+
keyValues[i] = keyValue = keyPrefix + key.call(node, node.__data__, i, group);
46+
if (!nodeByKeyValue[keyValue]) {
6847
nodeByKeyValue[keyValue] = node;
6948
}
7049
}
7150
}
7251

73-
// Now clear the update array and initialize to the new length.
74-
update.length = 0, update.length = dataLength;
75-
76-
// Compute the keys for each datum.
52+
// Compute the key for each datum.
53+
// If multiple data have the same key, only the first one counts.
7754
for (i = 0; i < dataLength; ++i) {
7855
keyValue = keyPrefix + key.call(parent, data[i], i, data);
7956

@@ -97,46 +74,53 @@ function bindKey(parent, update, enter, exit, data, key) {
9774

9875
// Take any remaining nodes that were not bound to data,
9976
// and place them in the exit selection.
100-
for (i = 0; i < nodeLength; ++i) {
101-
if ((node = nodeByKeyValue[keyValues[i]]) !== true) {
77+
for (i = 0; i < groupLength; ++i) {
78+
if ((node = group[i]) && (nodeByKeyValue[keyValues[i]] !== true)) {
10279
exit[i] = node;
10380
}
10481
}
10582
}
10683

10784
export default function(value, key) {
10885
if (!value) {
109-
var data = new Array(this.size()), i = -1;
110-
this.each(function(d) { data[++i] = d; });
86+
data = new Array(this.size()), j = -1;
87+
this.each(function(d) { data[++j] = d; });
11188
return data;
11289
}
11390

11491
var bind = key ? bindKey : bindIndex,
11592
parents = this._parents,
116-
update = arrayify(this),
117-
enter = (this._enter = this.enter())._groups,
118-
exit = (this._exit = this.exit())._groups;
93+
groups = this._groups;
11994

12095
if (typeof value !== "function") value = constant(value);
12196

122-
for (var m = update.length, j = 0; j < m; ++j) {
123-
var group = update[j],
124-
parent = parents[j];
97+
for (var m = groups.length, update = new Array(m), enter = new Array(m), exit = new Array(m), j = 0; j < m; ++j) {
98+
var parent = parents[j],
99+
group = groups[j],
100+
groupLength = group.length,
101+
data = value.call(parent, parent && parent.__data__, j, parents),
102+
dataLength = data.length,
103+
enterGroup = enter[j] = new Array(dataLength),
104+
updateGroup = update[j] = new Array(dataLength),
105+
exitGroup = exit[j] = new Array(groupLength);
125106

126-
bind(parent, group, enter[j], exit[j], value.call(parent, parent && parent.__data__, j, parents), key);
107+
bind(parent, group, enterGroup, updateGroup, exitGroup, data, key);
127108

128109
// Now connect the enter nodes to their following update node, such that
129110
// appendChild can insert the materialized enter node before this node,
130111
// rather than at the end of the parent node.
131-
for (var n = group.length, i0 = 0, i1 = 0, previous, next; i0 < n; ++i0) {
132-
if (previous = enter[j][i0]) {
112+
for (var i0 = 0, i1 = 0, previous, next; i0 < dataLength; ++i0) {
113+
if (previous = enterGroup[i0]) {
133114
if (i0 >= i1) i1 = i0 + 1;
134-
while (!(next = group[i1]) && ++i1 < n);
115+
while (!(next = updateGroup[i1]) && ++i1 < dataLength);
135116
previous._next = next || null;
136117
}
137118
}
138119
}
139120

121+
this._groups = update;
122+
(this._enter = new Selection(enter, parents))._update = this;
123+
this._exit = new Selection(exit, parents);
140124
return this;
141125
}
142126

test/selection/data-test.js

+76-1
Original file line numberDiff line numberDiff line change
@@ -190,7 +190,7 @@ tape("selection.data(values, function) passes the key function datum, index and
190190
.datum("foo");
191191

192192
d3.select(body).selectAll("node")
193-
.data(["foo", "bar"], function(d, i, nodes) { results.push([this, d, i, nodes.slice()]); return d || this.id; });
193+
.data(["foo", "bar"], function(d, i, nodes) { results.push([this, d, i, nodes]); return d || this.id; });
194194

195195
test.deepEqual(results, [
196196
[one, "foo", 0, [one, two]],
@@ -234,3 +234,78 @@ tape("selection.data(values, function) applies the order of the data", function(
234234
});
235235
test.end();
236236
});
237+
238+
tape("selection.data(values) does not modify the groups array in-place", function(test) {
239+
var document = jsdom.jsdom("<h1 id='one'></h1><h1 id='two'></h1>"),
240+
root = document.documentElement,
241+
one = document.querySelector("#one"),
242+
two = document.querySelector("#two"),
243+
selection = d3.select(root).selectAll("h1"),
244+
groups0 = selection._groups,
245+
group0 = groups0[0],
246+
updates1 = selection.data([1, 2, 3])._groups,
247+
update1 = updates1[0],
248+
enters1 = selection._enter._groups,
249+
enter1 = enters1[0],
250+
exits1 = selection._exit._groups,
251+
exit1 = exits1[0],
252+
updates2 = selection.data([1])._groups,
253+
update2 = updates2[0],
254+
enters2 = selection._enter._groups,
255+
enter2 = enters2[0],
256+
exits2 = selection._exit._groups,
257+
exit2 = exits2[0];
258+
test.deepEqual(group0, [one, two]);
259+
test.deepEqual(groups0, [[one, two]]);
260+
test.deepEqual(update1, [one, two,]);
261+
test.deepEqual(updates1, [[one, two,]]);
262+
test.deepEqual(enter1, [,, {__data__: 3, _next: null, _parent: root, namespaceURI: "http://www.w3.org/1999/xhtml", ownerDocument: document}]);
263+
test.deepEqual(enters1, [[,, {__data__: 3, _next: null, _parent: root, namespaceURI: "http://www.w3.org/1999/xhtml", ownerDocument: document}]]);
264+
test.deepEqual(exit1, [,]);
265+
test.deepEqual(exits1, [[,]]);
266+
test.deepEqual(update2, [one]);
267+
test.deepEqual(updates2, [[one]]);
268+
test.deepEqual(enter2, [,]);
269+
test.deepEqual(enters2, [[,]]);
270+
test.deepEqual(exit2, [, two]);
271+
test.deepEqual(exits2, [[, two]]);
272+
test.end();
273+
});
274+
275+
tape("selection.data(values, key) does not modify the groups array in-place", function(test) {
276+
var document = jsdom.jsdom("<h1 id='one'></h1><h1 id='two'></h1>"),
277+
root = document.documentElement,
278+
one = document.querySelector("#one"),
279+
two = document.querySelector("#two"),
280+
selection = d3.select(root).selectAll("h1"),
281+
key = function(d, i) { return i; },
282+
groups0 = selection._groups,
283+
group0 = groups0[0],
284+
updates1 = selection.data([1, 2, 3], key)._groups,
285+
update1 = updates1[0],
286+
enters1 = selection._enter._groups,
287+
enter1 = enters1[0],
288+
exits1 = selection._exit._groups,
289+
exit1 = exits1[0],
290+
updates2 = selection.data([1], key)._groups,
291+
update2 = updates2[0],
292+
enters2 = selection._enter._groups,
293+
enter2 = enters2[0],
294+
exits2 = selection._exit._groups,
295+
exit2 = exits2[0];
296+
test.deepEqual(group0, [one, two]);
297+
test.deepEqual(groups0, [[one, two]]);
298+
test.deepEqual(update1, [one, two,]);
299+
test.deepEqual(updates1, [[one, two,]]);
300+
test.deepEqual(enter1, [,, {__data__: 3, _next: null, _parent: root, namespaceURI: "http://www.w3.org/1999/xhtml", ownerDocument: document}]);
301+
test.deepEqual(enters1, [[,, {__data__: 3, _next: null, _parent: root, namespaceURI: "http://www.w3.org/1999/xhtml", ownerDocument: document}]]);
302+
test.deepEqual(exit1, [,]);
303+
test.deepEqual(exits1, [[,]]);
304+
test.deepEqual(update2, [one]);
305+
test.deepEqual(updates2, [[one]]);
306+
test.deepEqual(enter2, [,]);
307+
test.deepEqual(enters2, [[,]]);
308+
test.deepEqual(exit2, [, two]);
309+
test.deepEqual(exits2, [[, two]]);
310+
test.end();
311+
});

0 commit comments

Comments
 (0)