Selection module structure

  • Select elements
  • Modify the element
  • Add data (content of this chapter)
  • Handle events
  • The control flow
  • A local variable
  • The namespace

Add data

Join notebookThinking With Joins Joins are introduced in this section

selection.data([data[, key]])

Select an update state from a selection of data arrays. Select an update state from a selection of data arrays. Select an update state from a selection of data arrays. Or a function that returns an array, stored in the selector’s __data__ property, to make the data “sticky.” In combination with Join, Enter, exit, Append, and remove, elements can be added, updated, and removed in a data-driven manner, as shown in the following example:

const matrix = [ [11975, 5871, 8916, 2868], [ 1951, 10048, 2060, 6171], [ 8010, 16145, 8090, 8045], [ 1013, 990, 940, 6907]]; evs.select("body")
  .append("table")
  .selectAll("tr")
  .data(matrix)
  .join("tr")
  .selectAll("td")
  .data(d => d)
  .join("td")
    .text(d => d);
Copy the code

In this example, the number of table rows is added by the length of the matrix. If key is not specified, the numbers are added in their own order, and if key is a function, the string returned by executing that function for each element is added to the current element (in this case, just the keyValue in the source code, similar to a tag bit), and the data nodes are merged into The Enter. Data source analysis if only read the document is really confused, here is the principle of the design of the selector to add several array class attributes, now can be understood as data, Enter, update, exit, when we data a data, will first according to the data in the data of the three array to add elements, Update the data of the existing node that has been bound to the data. Enter the data that has been updated removes the remaining data of the updated node, which is equivalent to adding a new element node. Exit will store which nodes cannot match the data, or the length of the data is so large that the redundant existing nodes are equivalent to deleting some nodes. Of course, the author finally deleted the update because it is just a procedure variable and does not need to be stored. You can simply say that Enter adds binding data to the original node to facilitate the addition of nodes driven by that data, exit stores the deletion of nodes that do not match the data, and update is the final return value. Source code: Skip it if you don’t want to, but I think a look at the code makes it clear:

import {Selection} from "./index.js";
import {EnterNode} from "./enter.js";
import constant from '.. /constant.js';

var keyPrefix='$'; // Prevent keys like __proto__ // without entering keysfunction bindIndex(parent,group,enter,update,exit,data){ var i=0, node, groupLength=group.length, dataLength=data.length; // Add a non-empty node to an update array. // Add an empty node to enterfor(; i<dataLength; ++i){if(node=group[i]){
            node.__data__ = data[i];
            update[i]=node;
        }else{ enter[i]=new EnterNode(parent,data[i]); }} // Redundant nodes that cannot add data are processed here. joinexit
    for(; i<groupLength; ++i){if(node = group[i]){
            exit[i]=node; }}} // The key function input casefunction bindKey(parent,group,enter,update,exit,data,key){var I, node, nodeByKeyValue={}hashGroupLength =group.length, dataLength=data.length, keyValues=new Array(groupLength), keyValue; // This is the case where the original array of nodes is returned by calling the key function, and the extra valuekey is addedexit// Calculate the key value for each node // If multiple nodes have the same key, add it repeatedlyexit
    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; // Use the key function to process datum. // If there are already nodes associated with this key, add it to update. // If there are no nodes associated with this key, add it to update. Or if the key is repeated, enterfor(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]; //to update nodeByKeyValue[keyValue]=null; // Add update and delete;else{ enter[i]=new EnterNode(parent,data[i]); }} // Put the remaining unbound nodes inexit
    for(i=0; i<groupLength; ++i){if((node=group[I])&&(nodeByKeyValue[keyValues[I]]===node)){// Here calculate if the original node call key function valueKey // It is also possible to duplicate the keyValue obtained by calling the key function of the newly added data, so // adds itexit
            exit[i]=node; }}}export default function (value,key) {
    if(! Data =new Array(this.size()),j=-1; data=new Array(this.size()),j=-1; this.each(function(d){ data[++j]=d; }); / / each of the callback, the callback. The call (the node, the node __data__, I, group);return data;
    }
    var bind=key?bindKey:bindIndex,
        parents=this._parents,
        groups=this._groups;
    if(typeof value ! = ="function") value=constant(value); // Add the array enter, update,exitThe original data is stored in datafor(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,enterGroup,data,key); // All you can do now is add the node inside Enter to the update //for(var i0=0,i1=0,previous,next; i0<dataLength; ++i0){if(previous = enterGroup[i0]){
                if(i0 >= i1) i1=i0+1; // Set the initial value of i1while(! (next = updateGroup[i1]) && ++i1 < dataLength) ; previous._next = next || null; } } } update=new Selectoin(update,parent); update._enter = enter; update._exit=exit;
    return update;
}
Copy the code

For example, this document:

<div id="Ford"></div>
<div id="Jarrah"></div>
<div id="Kwon"></div>
<div id="Locke"></div>
<div id="Reyes"></div>
<div id="Shephard"></div>
Copy the code

Add data via key function:

const data = [
  {name: "Locke", number: 4},
  {name: "Reyes", number: 8},
  {name: "Ford", number: 15},
  {name: "Jarrah", number: 16},
  {name: "Shephard", number: 23},
  {name: "Kwon", number: 42}
];
d3.selectAll("div")
  .data(data, function(d) { return d ? d.name : this.id; })
    .text(d => d.number);

Copy the code

The key function checks whether d exists and returns name or ID, assuming that the current selector element has no data bound. The datum on the element is null. If the element has data bound, d is null. Update and enter are performed in data order, and exit retains the same order. If a key is specified, the order of the elements in the selector may not be the same as the order of the elements in the document. In this case, you need to sort the document using the previously mentioned selection.order or sort. Let’s Make a Bar Chart, IIObject Constancy returns an array of data for the selector element if no data is specified. This method cannot be used to clear binding data, use selection.datum.

selection.join(enter[, update][, exit])

Add, delete, and reorder elements according to the previously bound data data. It is the display replacement of Enter, exit, Append, remove, and oreder. The code implementation also encapsulates these functions. Use the following

svg.selectAll("circle")
  .data(data)
  .join("circle")
    .attr("fill"."none")
    .attr("stroke"."black");
Copy the code

And you can pass in functions to control each operation:

svg.selectAll("circle")
  .data(data)
  .join(
    enter => enter.append("circle").attr("fill"."green"),
    update => update.attr("fill"."blue")
  )
    .attr("stroke"."black");
Copy the code

Changes to the DOM can be minimized to optimize performance by separating Enter and update, and by adding a key function to data. You can also animate by creating transitions in Enter, update, and exit, create transitions with selection. Call to avoid breaking the method chain, or return an undefined Enter and update organization merge.

selection.enter()

Returns the enter state of the selector, with no DOM element placeholder for each bound datum. If no selection.data is dropped, the return value is null. The enter state of the selector is usually used to create elements missing from the new data as follows:

const div = d3.select("body")
  .selectAll("div")
  .data([4, 8, 15, 16, 23, 42])
  .enter().append("div")
    .text(d => d);
Copy the code

If the body is empty, the program creates six divs, specifying their text content as associative data (mandatory string type) based on the order of the array data.

<div>4</div>
<div>8</div>
<div>15</div>
<div>16</div>
<div>23</div>
<div>42</div>
Copy the code

Conceptually, the placeholder for the Enter stub is a pointer to the parent element (body in the example above). This method is typically used to add an element, which is then merged with the selector for the Update state to apply to both Enter and Update states. Enter is implemented by defining an enterNode object in Enter.js, adding this object to the selector, and entering the enter state mentioned above returns a new selector. This selector construct is passed in the._enter of the previous link selector and its parent node. The source code is as follows

import sparse from './sparse.js'
import {Selection} from '.index.js'

export default function () {
    returnnew Selection(this._enter || this._groups.map(sparse),this._parents); } // The enterNode object is defined here, which is also the constructor plus the prototype patternexport function EnterNode(parent,datum) {
    this.ownerDocument = parent.ownerDocument;
    this.namespaceURI=parent.namespaceURI;
    this._next = null;
    this._parent = parent;
    this.__data__=datum;
}
EnterNode.prototype={
    constructor:EnterNode,
    appendChild:function(child){  return this._parent.insertBefore(child, this._next); },
    insertBefore:function(child,next){ return this._parent.insertBefore(child,next); },
    querySelector:function(selector){ return this._parent.querySelector(selector); },
    querySelectorAll:function(selector) { returnthis._parent.querySelectorAll(selector); }};Copy the code

selection.exit()

Returns the deleted Selection element, the node to which no data is added to the document. Usually used to remove old data and superfluous elements before adding new arrays.

div = div.data([1, 2, 4, 8, 16, 32], d => d);
Copy the code

Update (1,2,32); update (enter, 1,2,32);

div.enter().append("div").text(d => d);
Copy the code

Delete 15,23,42 from old data:

div.exit().remove();
Copy the code

The document now looks like this:

<div>1</div>
<div>2</div>
<div>4</div>
<div>8</div>
<div>16</div>
<div>32</div>
Copy the code

The DOM is in the same order as the data because old and new data are in the same order. If the new order is different, use selection.order to reorder.

selection.datum([value])

Gets or sets the binding data for each element. This method is no different from selection.data. The internal implementation of this.node().__data__ actually gets or sets the data directly. When value is specified, it is assigned if it is a constant, and the return value is set to data (the name of the data binding on each node is __data__). Null removes the data bound to that element. If no value is specified, returns the data for the first non-empty node binding. This is useful when there is only one node. This method works well for custom attributes in H5, such as given the following element:

<ul id="list">
  <li data-username="shawnbot">Shawn Allen</li>
  <li data-username="mbostock">Mike Bostock</li>
</ul>
Copy the code

This method sets the data bound on the element to the built-in dataset property.

selection.datum(function() { return this.dataset; })
Copy the code

conclusion

This is a resolution of the principle of the data binding, and the several state of the data, when we analyze the data, the most effective several operation adding data, deleting data, updating data, the author of the d3 with this a few Jane answer all API is solved, in binding on the dom, makes the dom can be also add or remove update according to the data. The basic principle is that selection objects, We add the attributes Enter (an array of data nodes to which the element is added), exit(an array of deleted nodes), UPDATE (an array of data-bound nodes updated based on the new data), and __data__(a data attribute on a single node that holds the data content) to Selection. In these apis, data is required. After data, selectin can perform Enter and exit operations, datum can view or set data values, and an abstract join function can simplify the enter and exit operations and optimize the speed. High-level interface: when I use it, I can directly use data.enter. For data binding, and other operations can be added when necessary.

Deep reading:

Source and analysis: github.com/dongoa/evs-…