Chapter 01Selections

As we’ll soon see, more often than not, when we want to change a attribute or property of an element, we’ll want to do the same for a whole set of elements in the DOM. Sometimes we can accomplish this by calling getElementsByClassName or getElementsByTagName and then use a for-loops to iterate over the selected elements. Bostock has engineered a smarter way: Selections.

The tutorial How Selections Work written by Bostock describes in depth how the d3.selection object works.  It is highly recommended that you read this tutorial after you’ve read this one.

The D3 API has 3 methods that can be used to select elements from the DOM. These are:

Each returns zero or more elements from the DOM in a d3.selection object. The d3.selection type is a subclass of array and contains methods that when called effect all of the elements contained in the selection object.

d3.select(selector) returns a selection containing zero or one element or node. A selector arguement can be either an string that holds any valid CSS selector or a reference to a node. If the selector is a string, it returns a selection containing the first element found in the DOM that satisfies the CSS selector criteria.  If no element is found then it returns an empty selection.  If the selector is a reference to a node then it returns a section object containing that single node.

d3.selectAll(selector) returns a selection of zero or more elements or nodes. The method takes as an argument a string that holds a CSS selector, an array of nodes, or a pseudo-array like a NodeList.  If the argument is a string, the method returns a selection containing all of the elements that match the CSS selector defined by the string.  If no elements match, the selection object that is returned is empty.  If the selector contains references to nodes then the method returns a selection that contains the nodes.

To illustrate various ways that we can use these methods, lets suppose we have 5 circles rendered in an svg as shown below.

<svg width="425" height="100" >
  <circle class="lightblue" cx="50" cy="50" r="25" />
  <circle class="lightblue" cx="125" cy="50" r="25" />
  <circle class="pink" cx="200" cy="50" r="25" />
  <circle id="secondPink" class="pink" cx="275" cy="50" r="25"" />
  <circle cx="350" cy="50" r="25" fill="aquamarine" />
</svg>

In the script below we select different subsets of the 5 circles using the selection methods. Note that we don’t do anything with the selections just yet. We’re just demonstrating how to select sets of elements.

let firstBlueCircle = d3.select(".lightblue");
let allBlueCircles = d3.selectAll(".lightblue");
let secondPinkCircle = d3.select("#secondPink");
let allCircles = d3.selectAll("circle");

In the first statement we set firstBlueCircle equal to the selection object that contains a single element, the first element having the class name lightblue. The second statement uses the same CSS selector, but calls selectAll to return a selection containing all of the elements having a class name lightblue. The third statement sets the variable secondPinkCircle to the selection object containing a single element, namely the element whose id attribute is set to secondPink. The fourth statement retrieves a selection containing all of the circle elements in the web page’s DOM.

Remember that each of the select methods discussed above return a selection object, not a Node object or a set of Node objects. Also, any valid CSS selector can be passed to select and selectAll.

d3.selection() is used to retrieve a selection object that contains only the root document element (i.e. document.documentElement) of the web page.

Selecting Descendant Elements

The selection type has two methods that allow you to select zero or more descendant elements based on the elements in the current selection.  Per the API, these methods are:

The SVG element below uses <g> elements to group the circles in each row. In order to get all of the circles that are decendents of the first <g> element we chain calls to selection.select and selection.selectAll. We can then modify the attributes of the new selection.

d3.select("g")
  .selectAll("circle")
  .attr("stroke", "gray")
  .attr("stroke-width", "3");
<script>
  function nestedSelect() {
    d3.select("g")
      .selectAll("circle")
      .attr("stroke", "gray")
      .attr("stroke-width", "3");
  }
</script>

<svg width="160" height="100">
  <g>
    <circle r="20" cx="30" cy="30" fill="lightblue" />
    <circle r="20" cx="80" cy="30" fill="lightblue" />
    <circle r="20" cx="130" cy="30" fill="lightblue" />
  </g>
  <g>
    <circle r="20" cx="30" cy="80" fill="lightblue" />
    <circle r="20" cx="80" cy="80" fill="lightblue" />
    <circle r="20" cx="130" cy="80" fill="lightblue" />
  </g>
</svg>
<button id="nestedSelectButton" onclick="nestedSelect()">Add Stroke</button>

Filtering Selections

The selection.filter(filter) method takes a filter as an argument and returns a selection containing a subset of the objects in the selection on which it is called. The filter argument can be either a selection string as discussed above or a function. If the filter is a selection string, the method returns the elements in the selection that match the selection string.

If the filter is a function, the function is called for each element in the selection, in order. When called for an given element, it is passed three arguments: the datum joined to the element (d), an integer specifying the index of the element in the current group of elements that the element belong to, and the group itself. The elements for which the function returns true are retained in the selection and the others are removed.

In the example below, the 5 circle elements have radii between 5 and 25.  We select all of the circle elements, filter the selection down to only those with radii greater than or equal to 20, then remove the elements left in the selection.

<script>
  function filterOnRadius() {
    d3.selectAll("#filterOnRadius circle")
        .filter((d,i,nodes) => nodes[i].getAttribute("r") >= 20)
        .remove();
  }
</script>

<button onclick="filterOnRadius()">Filter</button>
<svg id="filterOnRadius" width="400" height="100">
  <circle cx="50" cy="50" r="5" fill="pink" />
  <circle cx="125" cy="50" r="10" fill="pink" />
  <circle cx="200" cy="50" r="15" fill="pink" />
  <circle cx="275" cy="50" r="20" fill="pink" />
  <circle cx="350" cy="50" r="25" fill="pink" />
</svg>

Selection Size and Elements

The d3.selection type has a number of methods that allow us retrieve the size and elements in the selection.  Per the API these methods are:

Note that these four methods do not return a selection object.