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:
Name | Behaviour | Example |
---|---|---|
.style | Update the style | d3.selectAll('circle').style('fill', 'red') |
.attr | Update an attribute | d3.selectAll('rect').attr('width', 10) |
.classed | Add/remove a class attribute | d3.select('.item').classed('selected', true) |
.property | Update an element's property | d3.selectAll('.checkbox').property('checked', false) |
.text | Update the text content | d3.select('div.title').text('My new book') |
.html | Change the html content | d3.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 name | Description |
---|---|
click | Element has been clicked |
mouseenter | Mouse pointer has moved onto the element |
mouseover | Mouse pointer has moved onto the element or its children |
mouseleave | Mouse pointer has moved off the element |
mouseout | Mouse pointer has moved off the element or its children |
mousemove | Mouse 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;
});