Selections

D3 selections allow DOM elements to be selected in order to do something with them, be it changing style, modifying their attributes, performing data-joins or inserting/removing elements.

For example, given 5 circles:

we can use d3.selectAll to select the circles and .style and .attr to modify them:

d3.selectAll('circle')
  .style('fill', 'orange')
  .attr('r', function() {
    return 10 + Math.random() * 40;
  });

Making selections

D3 has two functions to make selections d3.select and d3.selectAll.

d3.select selects the first matching element whilst d3.selectAll selects all matching elements. Each function takes a single argument which specifies the selector string.

For example to select all elements with class item use d3.selectAll('.item').

Modifying elements

Once we’ve made a selection we can modify the elements in it using the following functions:

NameBehaviourExample
.styleUpdate the styled3.selectAll('circle').style('fill', 'red')
.attrUpdate an attributed3.selectAll('rect').attr('width', 10)
.classedAdd/remove a class attributed3.select('.item').classed('selected', true)
.propertyUpdate an element's propertyd3.selectAll('.checkbox').property('checked', false)
.textUpdate the text contentd3.select('div.title').text('My new book')
.htmlChange the html contentd3.select('.legend').html('<div class="block"></div><div>0 - 10</div>')

Whether .select or .selectAll is used, all elements in the selection will be modified.

Here’s an example of all of these functions in use:

Updating selections with functions

In addition to passing constant values such as red, 10 and true to .style, .attr, .classed, .property, .text and .html we can pass in a function:

d3.selectAll('circle')
  .attr('cx', function(d, i) {
    return i * 100;
  });

The function typically accepts two arguments d and i. The first argument d is the joined data (see the data joins section) and i is the index of the element within the selection.

If we want to update elements in a selection according to their position within the selection, we can use the i argument. For example to position some rect elements horizontally we can use:

d3.selectAll('rect')
  .attr('x', function(d, i) {
    return i * 40;
  });

In the majority of cases when functions are passed in, anonymous functions are used. However we can also use named functions e.g.

function positionRects(d, i) {
  return i * 40;
}

d3.selectAll('rect')
  .attr('x', positionRects);

Handling events

We can add event handlers to selected elements using .on which expects a callback function into which is passed two arguments d and i. As before, d is the joined data (see the data joins section) and i is the index of the element within the selection.)

The most common events include (see MDN event reference for more details):

Event nameDescription
clickElement has been clicked
mouseenterMouse pointer has moved onto the element
mouseoverMouse pointer has moved onto the element or its children
mouseleaveMouse pointer has moved off the element
mouseoutMouse pointer has moved off the element or its children
mousemoveMouse pointer has moved over the element

Let’s set up an event handler to update a status element with the index of the clicked element:

d3.selectAll('circle')
  .on('click', function(d, i) {
    d3.select('.status')
      .text('You clicked on circle ' + i);
  });

In the event callback function the this variable is bound to the DOM element. This allows us to do things such as:

d3.selectAll('circle')
  .on('click', function(d, i) {
    d3.select(this)
      .style('fill', 'orange');
  });

Note that this is a DOM element and not a D3 selection so if we wish to modify it using D3 we must first select it using d3.select(this).

Inserting and removing elements

Elements can be added to a selection using .append and .insert whilst elements can be removed using .remove.

.append appends an element to the children of each element in the selection. The first argument specifies the type of element.

As an example let’s start with 3 g elements, each containing a circle:

<g class="item" transform="translate(0, 0)">
  <circle r="40" />
</g>
<g class="item" transform="translate(120, 0)">
  <circle r="40" />
</g>
<g class="item" transform="translate(240, 0)">
  <circle r="40" />
</g>

We can append a text element to each using:

d3.selectAll('g.item')
  .append('text')
  .text(function(d, i) {
    return i + 1;
  });

resulting in a text being added to each g.item:

<g class="item" transform="translate(0, 0)">
  <circle r="40" />
  <text>1</text>
</g>
<g class="item" transform="translate(120, 0)">
  <circle r="40" />
  <text>2</text>
</g>
<g class="item" transform="translate(240, 0)">
  <circle r="40" />
  <text>3</text>
</g>

(.append is commonly used in the context of enter/exit where it has different behaviour.)

.insert is similar to .append but it allows us to specify a before element to which, you guessed it, the new element is attached.

Therefore if we run the same example again, but choosing to insert the text element before the circle element we get:

d3.selectAll('g.item')
  .insert('text', 'circle')
  .text(function(d, i) {
    return i + 1;
  });

and the DOM will look like:

<g class="item" transform="translate(0, 0)">
  <text>1</text>
  <circle r="40" />
</g>
<g class="item" transform="translate(120, 0)">
  <text>2</text>
  <circle r="40" />
</g>
<g class="item" transform="translate(240, 0)">
  <text>3</text>
  <circle r="40" />
</g>

.remove removes all the elements in a selection. For example, given some circles, we can remove them using:

d3.selectAll('circle')
  .remove();

Chaining

Most selection functions return the selection, meaning that selection functions such as .style, .attr and .on can be chained:

d3.selectAll('circle')
  .style('fill', 'orange')
  .attr('r', 20)
  .on('click', function(d, i) {
    d3.select('.status')
      .text('You clicked on circle ' + i);
  });

Each and call

.each allows a function to be called on each element of a selection and .call allows a function to be called on the selection itself.

In the case of .each D3 passes in the joined datum (usually represented by d) and the index (usually represented by i). Not only can .each enable reusable components but it also allows computations to be shared across calls to .style, .attr etc.

Here’s an example of using .each to call a reusable component:

function addNumberedCircle(d, i) {
  d3.select(this)
    .append('circle')
    .attr('r', 40);

  d3.select(this)
    .append('text')
    .text(i + 1)
    .attr('y', 50)
    .attr('x', 30);
}

d3.selectAll('g.item')
  .each(addNumberedCircle);

Here’s an example of .each used for the latter:

d3.selectAll('circle')
  .each(function(d, i) {
    var odd = i % 2 === 1;

    d3.select(this)
      .style('fill', odd ? 'orange' : '#ddd')
      .attr('r', odd ? 40 : 20);
  });

In the case of .call D3 passes in the selection itself. This is a common pattern for reusable components.

In the following example we create a similar component to before using .call. This time the selection gets passed into the component (rather than d and i):

function addNumberedCircle(selection) {
  selection
    .append('circle')
    .attr('r', 40);

  selection
    .append('text')
    .text(function(d, i) {
      return i + 1;
    })
    .attr('y', 50)
    .attr('x', 30);
}

d3.selectAll('g.item')
  .call(addNumberedCircle);

Filtering and sorting selections

We can filter a selection using .filter. A function is usually passed into .filter which returns true if the element should be included. .filter returns the filtered selection.

In this example we filter through even-numbered elements and colour them orange:

d3.selectAll('circle')
  .filter(function(d, i) {
    return i % 2 === 0;
  })
  .style('fill', 'orange');

Sorting only really makes sense if data has been joined to the selection, so please read up on data joins first.

We can sort elements in a selection by calling .sort and passing in a comparator function. The comparator function has two arguments, usually a and b, which represent the datums on the two elements being compared. If the comparator function returns a negative number, a will be placed before b and if positive, a will be placed after b.

Thus if we have the following data joined to a selection:

[
  {
    "name": "Andy",
    "score": 37
  },
  {
    "name": "Beth",
    "score": 39
  },
  {
    "name": "Craig",
    "score": 31
  },
  {
    "name": "Diane",
    "score": 35
  },
  {
    "name": "Evelyn",
    "score": 38
  }
]

we can sort by score using:

  d3.selectAll('.person')
    .sort(function(a, b) {
      return b.score - a.score;
    });

Comments