From 4223e4eab799705782b9a8d9112eb007aa5763cd Mon Sep 17 00:00:00 2001 From: Ben Sidhom Date: Tue, 17 Dec 2024 19:06:37 -0800 Subject: [PATCH] Make .data() binding work on iterable protocol The current data binding implementation relies on random array-style access, but only ever accesses data sequentially. Some data structures (such as linked lists) cannot easily expose random access. Moreover, since this specifically uses bracket indexing, it only accepts `Array`s or first-class array-like objects. Making a custom data structure indexable this way requires either an expensive `Proxy` (indexes have to be round-tripped from integers to strings on every access) or else requires the custom class to add explicit integer properties for each contained item; this adds memory bloat linear in the size of the data structure itself and also requires unnecessary bookkeeping. If random access were required, it might be reasonable to require some cheaply-implemented but customizable accessor method (see, for example Array.at()). However, since this is not the case, it makes more sense to instead only require that input data types be iterable. This opens up efficient implementations for structures such as singly-linked lists and other purely applicative structures. Backward compatibility can be maintained by adding a cheap wrapper to implement iteration in terms of sequential indexing. --- src/selection/data.js | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/src/selection/data.js b/src/selection/data.js index 8eb0713..baab56f 100644 --- a/src/selection/data.js +++ b/src/selection/data.js @@ -5,19 +5,19 @@ import constant from "../constant.js"; function bindIndex(parent, group, enter, update, exit, data) { var i = 0, node, - groupLength = group.length, - dataLength = data.length; + groupLength = group.length; // Put any non-null nodes that fit into update. // Put any null nodes into enter. // Put any remaining data into enter. - for (; i < dataLength; ++i) { + for (const datum of data) { if (node = group[i]) { - node.__data__ = data[i]; + node.__data__ = datum; update[i] = node; } else { - enter[i] = new EnterNode(parent, data[i]); + enter[i] = new EnterNode(parent, datum); } + i += 1; } // Put any non-null nodes that don’t fit into exit. @@ -33,7 +33,6 @@ function bindKey(parent, group, enter, update, exit, data, key) { node, nodeByKeyValue = new Map, groupLength = group.length, - dataLength = data.length, keyValues = new Array(groupLength), keyValue; @@ -53,15 +52,17 @@ function bindKey(parent, group, enter, update, exit, data, key) { // Compute the key for each datum. // If there a node associated with this key, join and add it to update. // If there is not (or the key is a duplicate), add it to enter. - for (i = 0; i < dataLength; ++i) { - keyValue = key.call(parent, data[i], i, data) + ""; + i = 0; + for (const datum of data) { + keyValue = key.call(parent, datum, i, data) + ""; if (node = nodeByKeyValue.get(keyValue)) { update[i] = node; - node.__data__ = data[i]; + node.__data__ = datum; nodeByKeyValue.delete(keyValue); } else { - enter[i] = new EnterNode(parent, data[i]); + enter[i] = new EnterNode(parent, datum); } + i += 1; } // Add any remaining nodes that were not bound to data to exit.