@mbostock mbostock released this Jun 24, 2016 · 56 commits to master since this release

Assets 3

Changes since D3 3.x

Selections no longer subclass Array using prototype chain injection; they are now plain objects, improving performance. The internal fields (selection._groups, selection._parents) are private; please use the documented public API to manipulate selections. The new selection.nodes method generates an array of all nodes in a selection.

Selections are now immutable: the elements and parents in a selection never change. (The elements’ attributes and content will of course still be modified!) The selection.sort and selection.data methods now return new selections rather than modifying the selection in-place. In addition, selection.append no longer merges entering nodes into the update selection; use selection.merge to combine enter and update after a data join. For example, the following general update pattern in 3.x:

var circle = svg.selectAll("circle").data(data) // UPDATE
    .style("fill", "blue");

circle.exit().remove(); // EXIT

circle.enter().append("circle") // ENTER; modifies UPDATE! 🌶
    .style("fill", "green");

circle // ENTER + UPDATE
    .style("stroke", "black");

Would be rewritten in 4.0 as:

var circle = svg.selectAll("circle").data(data) // UPDATE
    .style("fill", "blue");

circle.exit().remove(); // EXIT

circle = circle.enter().append("circle") // ENTER
    .style("fill", "green")
  .merge(circle) // ENTER + UPDATE
    .style("stroke", "black");

This change is discussed further in What Makes Software Good.

In 3.x, the selection.enter and selection.exit methods were undefined until you called selection.data, resulting in a TypeError if you attempted to access them. In 4.0, now they simply return the empty selection if the selection has not been joined to data.

In 3.x, selection.append would always append the new element as the last child of its parent. A little-known trick was to use selection.insert without specifying a before selector when entering nodes, causing the entering nodes to be inserted before the following element in the update selection. In 4.0, this is now the default behavior of selection.append; if you do not specify a before selector to selection.insert, the inserted element is appended as the last child. This change makes the general update pattern preserve the relative order of elements and data. For example, given the following DOM:

<div>a</div>
<div>b</div>
<div>f</div>

And the following code:

var div = d3.select("body").selectAll("div")
  .data(["a", "b", "c", "d", "e", "f"], function(d) { return d || this.textContent; });

div.enter().append("div")
    .text(function(d) { return d; });

The resulting DOM will be:

<div>a</div>
<div>b</div>
<div>c</div>
<div>d</div>
<div>e</div>
<div>f</div>

Thus, the entering c, d and e are inserted before f, since f is the following element in the update selection. Although this behavior is sufficient to preserve order if the new data’s order is stable, if the data changes order, you must still use selection.order to reorder elements.

There is now only one class of selection. 3.x implemented enter selections using a special class with different behavior for enter.append and enter.select; a consequence of this design was that enter selections in 3.x lacked certain methods. In 4.0, enter selections are simply normal selections; they have the same methods and the same behavior. Enter nodes now implement node.appendChild, node.insertBefore, node.querySelector, and node.querySelectorAll.

The selection.data method has been changed slightly with respect to duplicate keys. In 3.x, if multiple data had the same key, the duplicate data would be ignored and not included in enter, update or exit; in 4.0 the duplicate data is always put in the enter selection. In both 3.x and 4.0, if multiple elements have the same key, the duplicate elements are put in the exit selection. Thus, 4.0’s behavior is now symmetric for enter and exit, and the general update pattern will now produce a DOM that matches the data even if there are duplicate keys.

Selections have several new methods! Use selection.raise to move the selected elements to the front of their siblings, so that they are drawn on top; use selection.lower to move them to the back. Use selection.dispatch to dispatch a custom event to event listeners.

When called in getter mode, selection.data now returns the data for all elements in the selection, rather than just the data for the first group of elements. The selection.call method no longer sets the this context when invoking the specified function; the selection is passed as the first argument to the function, so use that. The selection.on method now accepts multiple whitespace-separated typenames, so you can add or remove multiple listeners simultaneously. For example:

selection.on("mousedown touchstart", function() {
  console.log(d3.event.type);
});

The arguments passed to callback functions has changed slightly in 4.0 to be more consistent. The standard arguments are the element’s datum (d), the element’s index (i), and the element’s group (nodes), with this as the element. The slight exception to this convention is selection.data, which is evaluated for each group rather than each element; it is passed the group’s parent datum (d), the group index (i), and the selection’s parents (parents), with this as the group’s parent.

The new d3.local provides a mechanism for defining local variables: state that is bound to DOM elements, and available to any descendant element. This can be a convenient alternative to using selection.each or storing local state in data.

The d3.ns.prefix namespace prefix map has been renamed to d3.namespaces, and the d3.ns.qualify method has been renamed to d3.namespace. Several new low-level methods are now available, as well. d3.matcher is used internally by selection.filter; d3.selector is used by selection.select; d3.selectorAll is used by selection.selectAll; d3.creator is used by selection.append and selection.insert. The new d3.window returns the owner window for a given element, window or document. The new d3.customEvent temporarily sets d3.event while invoking a function, allowing you to implement controls which dispatch custom events; this is used by d3-drag, d3-zoom and d3-brush.

For the sake of parsimony, the multi-value methods—where you pass an object to set multiple attributes, styles or properties simultaneously—have been extracted to d3-selection-multi and are no longer part of the default bundle. The multi-value map methods have also been renamed to plural form to reduce overload: selection.attrs, selection.styles and selection.properties.

See CHANGES for all D3 changes since 3.x.