d3.js selection.data([values[, key]]) source code parsing

Keywords: Javascript Attribute

Preface

Recently, I was learning d3.js. At the beginning of the selector section, there was a selection.data method. It was found that the second parameter of this method can be passed or not, and the second parameter is a function. However, there is no explanation of the usage of the second parameter in the document, so it is difficult to find the column, so I just compiled the source code.

Here, values is an array and key is a function. Most of the examples are written without passing the second function parameter. Today, I'll explain how to pass the second parameter.

Take a look at the code first

    <div class="Ford"></div>
    <div class="Jarrah"></div>
    <div class="Kwon"></div>
    <div class="Locke"></div>
    <div class="Reyes"></div>
    <div class="Shephard"></div>

There are div s in the body, and each of them has a class.

    const data = [
        { key: "Locke", number: 4 },
        { key: "Reyes", number: 8 },
        { key: "Ford", number: 15 },
        { key: "Jarrah", number: 16 },
        { key: "Shephard", number: 23 },
        { key: "Kwon", number: 42 }
    ];

    d3.selectAll("div")
        .data(data, function (d) {
            return d ? d.key : this.className;
        }).text(d=>d.key)

So the data is filled in according to the class name

    <div class="Ford">Ford</div>
    <div class="Jarrah">Jarrah</div>
    <div class="Kwon">Kwon</div>
    <div class="Locke">Locke</div>
    <div class="Reyes">Reyes</div>
    <div class="Shephard">Shephard</div>

And then the question? why? why do you write like this? Who taught you to write like this in the function? How do you know to write like this? I'm sorry I didn't find the documentation

It depends on the source code. The last code

Selection.prototype = selection.prototype = {
  constructor: Selection,
  select: selection_select,
  selectAll: selection_selectAll,
  filter: selection_filter,
  data: selection_data,
  ...

In this section, we find that there is a data method in the Selection.prototype, which corresponds to the selection [u data]. Then we will go to the selection [u data,

function selection_data(value, key) {
  if (!value) {
    data = new Array(this.size()), j = -1;
    this.each(function(d) { data[++j] = d; });
    return data;
  }

  var bind = key ? bindKey : bindIndex,
      parents = this._parents,
      groups = this._groups;

  if (typeof value !== "function") value = constant$1(value);

  for (var m = groups.length, update = new Array(m), enter = new Array(m), exit = new Array(m), j = 0; j < m; ++j) {
    var parent = parents[j],
        group = groups[j],
        groupLength = group.length,
        data = value.call(parent, parent && parent.__data__, j, parents),
        dataLength = data.length,
        enterGroup = enter[j] = new Array(dataLength),
        updateGroup = update[j] = new Array(dataLength),
        exitGroup = exit[j] = new Array(groupLength);

    bind(parent, group, enterGroup, updateGroup, exitGroup, data, key);

    // Now connect the enter nodes to their following update node, such that
    // appendChild can insert the materialized enter node before this node,
    // rather than at the end of the parent node.
    for (var i0 = 0, i1 = 0, previous, next; i0 < dataLength; ++i0) {
      if (previous = enterGroup[i0]) {
        if (i0 >= i1) i1 = i0 + 1;
        while (!(next = updateGroup[i1]) && ++i1 < dataLength);
        previous._next = next || null;
      }
    }
  }

  update = new Selection(update, parents);
  update._enter = enter;
  update._exit = exit;
  return update;
}

This function takes two parameters, value and key. When the key exists, VAR bind = key? Bindkey: bindindex. At this time, we first know that the bind assignment is bindkey, which is binding key, and then the following roughly means
A for loop, then processing variables, and then generating an update, return undo;
In this process, several parameters are passed to bind function,

    var parent = parents[j],
    group = groups[j],
    groupLength = group.length,
    data = value.call(parent, parent && parent.__data__, j, parents),
    dataLength = data.length,
    enterGroup = enter[j] = new Array(dataLength),
    updateGroup = update[j] = new Array(dataLength),
    exitGroup = exit[j] = new Array(groupLength);

People who have read the document at the beginning of d3 should go to enter, exit, group, update, which is the object generated by the selector;
enter means the part with more data than dom, the node to be inserted;
exit means the part with more dom than data, and the part to be deleted
update means the part to be updated.
If you don't understand, first look at the document, and then look at this in reverse.
So the bind function is bindkey. Take a look at what bindkey is

function bindKey(parent, group, enter, update, exit, data, key) {
  var i,
      node,
      nodeByKeyValue = {},
      groupLength = group.length,
      dataLength = data.length,
      keyValues = new Array(groupLength),
      keyValue;

  // Compute the key for each node.
  // If multiple nodes have the same key, the duplicates are added to exit.
  for (i = 0; i < groupLength; ++i) {
    if (node = group[i]) {
      keyValues[i] = keyValue = keyPrefix + key.call(node, node.__data__, i, group);
      if (keyValue in nodeByKeyValue) {
        exit[i] = node;
      } else {
        nodeByKeyValue[keyValue] = node;
      }
    }
  }

  // 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 = keyPrefix + key.call(parent, data[i], i, data);
    if (node = nodeByKeyValue[keyValue]) {
      update[i] = node;
      node.__data__ = data[i];
      nodeByKeyValue[keyValue] = null;
    } else {
      enter[i] = new EnterNode(parent, data[i]);
    }
  }

  // Add any remaining nodes that were not bound to data to exit.
  for (i = 0; i < groupLength; ++i) {
    if ((node = group[i]) && (nodeByKeyValue[keyValues[i]] === node)) {
      exit[i] = node;
    }
  }
}

You can see that it receives these variables, and then three loops. These three loops are all around nodeByKeyValue, exit. First, look at the first loop,

The group length is the length of the dom node obtained. Group is a dom array;
nodeByKeyValue starts as an empty object {}
exit doesn't need to worry about it first, which means to generate an array of dom nodes to be removed

keyValues[i] = keyValue = keyPrefix + key.call(node, node.__data__, i, group);

The meaning of this sentence
A constant in keyPrefix. It's' $'
key is the function parameter you passed
node = group[i] is the currently traversed dom node

function (d) {
            return d ? d.key : this.className;
        }

Within the function, this points to node. The function can take three parameters: node. Data, I, group,

Node. Data is the data on the dom node,
i index
group is a dom array

When we execute

d3.selectAll("div")
        .data(data, function (d) {
            return d ? d.key : this.className;
        }).text(d=>d.key)

The first result we can get is nodeByKeyValue

{
    $Ford: div.Ford
    $Jarrah: div.Jarrah
    $Kwon: div.Kwon
    $Locke: div.Locke
    $Reyes: div.Reyes
    $Shephard: div.Shephard
}

this.className in the function is the className of the node. At this time, there is no "data" attribute on each node, so you print d as undefined;

At this time, our function has been executed five times, because there are five dom nodes.

Remember nodeByKeyValue, useful later

So let's look at the second loop. It's obvious that data is traversed,

for (i = 0; i < dataLength; ++i) {
    keyValue = keyPrefix + key.call(parent, data[i], i, data);
    if (node = nodeByKeyValue[keyValue]) {
      update[i] = node;
      node.__data__ = data[i];
      nodeByKeyValue[keyValue] = null;
    } else {
      enter[i] = new EnterNode(parent, data[i]);
    }
  }

It's similar to the logic just now. We still need to execute our function
The first parameter is data[i], sir

keyValue = keyPrefix + key.call(parent, data[0], i, data);//$Locke
node = nodeByKeyValue[keyValue] // node=nodeByKeyValue.$Locke
node.__data__ = data[i]; // node.__data__ = { key: "Locke", number: 4 }

So far, we have seen that the data corresponds to the dom.

So far, the function has executed groupLength + dataLength times;

As for other code related codes, you can go and have a look, this time you won't see them.

My feeling: the source code of D3 is not advanced, and there will be redundant code in it.

Posted by sonoton345 on Wed, 11 Dec 2019 23:33:47 -0800