How to select HTML and SVG elements using D3 selections. This article shows how to select, insert, remove and modify elements, how to add event handlers, how to apply a function to selections and how to filter and sort selections.
D3 selections let you choose some HTML or SVG elements and change their style and/or attributes.
For example, if your index.html
file contains 5 SVG circle elements:
<svg width="760" height="140">
<g transform="translate(70, 70)">
<circle/>
<circle cx="120" />
<circle cx="240" />
<circle cx="360" />
<circle cx="480" />
</g>
</svg>
you can use selectAll
to select the circles then .style
to change their fill and .attr
to change their radius:
selectAll('circle')
.style('fill', 'orange')
.attr('r', function() {
return 10 + Math.random() * 40;
});
D3 selections also enable data joins (see the Data Joins chapter).
Making selections
The D3 module d3-selection
provides two functions select
and selectAll
.
To import select
use:
import { select } from 'd3-selection';
To import selectAll
use:
import { selectAll } from 'd3-selection';
Import both using:
import { select, selectAll } from 'd3-selection';
select
selects the first matching element whilst selectAll
selects all matching elements.
Both functions take a string as its only argument. The string specifies which elements to select and is in the form of a CSS selector string. For example:
div.item
#my-chart
g:first-child
For example to select all elements with class item
use:
selectAll('.item')
(If you’re not familiar with CSS selectors take a look at the CSS section in Fundamentals of HTML, SVG, CSS and JavaScript for Data Visualization.)
Modifying elements
You can modify a selection's elements using the following methods:
Name | Behaviour | Example |
---|---|---|
.style | Update the style | selectAll('circle').style('fill', 'red') |
.attr | Update an attribute | selectAll('rect').attr('width', 10) |
.classed | Add/remove a class attribute | select('.item').classed('selected', true) |
.property | Update an element's property | selectAll('.checkbox').property('checked', false) |
.text | Update the text content | select('div.title').text('My new book') |
.html | Change the html content | select('.legend').html('<div class="block"></div><div>0 - 10</div>') |
Note that the second argument of .classed
is a boolean. I often forget to include true
or false
and it raises an non-obvious error message!
Here's an example of all of these functions in use:
Updating selections with functions
You can also pass a function into a selection method. For example:
selectAll('circle')
.attr('cx', function(d, i) {
return i * 100;
});
The function accepts two arguments. The first argument (usually named d
) is the joined data (or 'datum') and will be covered in the data joins chapter. The second argument (usually named i
) is the index of the element within the selection.
To update elements in a selection according to their position within the selection use the i
argument. For example to position rect
elements horizontally use:
selectAll('rect')
.attr('x', function(d, i) {
return i * 40;
});
In the majority of cases anonymous functions are passed into selection methods. However you may altenatively use named functions. For example:
function positionRects(d, i) {
return i * 40;
}
selectAll('rect')
.attr('x', positionRects);
Event handling
You can add an event handler to a selection using the .on
method.
This method has two arguments:
-
the first is a string specifying the event type
-
the second is a function (a 'callback function') that's called when the event is triggered. This callback function has two arguments that are usually named
e
andd
.e
is the DOM event object andd
is the joined data (which will be covered in the data joins chapter).
Up to and including D3 version 5, the callback function was passed the datum
d
and indexi
. This is a breaking change.
The most common events include:
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 |
See MDN event reference for more details.
In the event callback function the this
variable is bound to the DOM element that triggered the event. This allows us to do things such as:
selectAll('circle')
.on('click', function(e, d) {
select(this)
.style('fill', 'orange');
});
Note that this
is a DOM element and not a D3 selection so if you wish to modify it using D3 you must first select it using select(this)
.
Note also that D3 does not define this
for arrow functions.
Inserting and removing elements
Elements can be added to a selection's elements using .append
and .insert
. Elements can be removed using .remove
.
.append
appends an element to each element in a selection. If the elements already have children, the new element will become the last child. 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>
Append a text
element to each using:
selectAll('g.item')
.append('text')
.text('A');
This results in a text
element being added to each g.item
:
<g class="item" transform="translate(0, 0)">
<circle r="40" />
<text>A</text>
</g>
<g class="item" transform="translate(120, 0)">
<circle r="40" />
<text>A</text>
</g>
<g class="item" transform="translate(240, 0)">
<circle r="40" />
<text>A</text>
</g>
.insert
is similar to .append
but it lets us provide a CSS selector string as the second argument. This specifies an element before which the new element is inserted.
In this example, .insert
is used and the second argument is 'circle'
:
selectAll('g.item')
.insert('text', 'circle')
.text('A');
This results in the text element being inserted before the circle:
<g class="item" transform="translate(0, 0)">
<text>A</text>
<circle r="40" />
</g>
<g class="item" transform="translate(120, 0)">
<text>A</text>
<circle r="40" />
</g>
<g class="item" transform="translate(240, 0)">
<text>A</text>
<circle r="40" />
</g>
.remove
removes all the elements in a selection from the page. For example, given some circles, you can remove them using:
selectAll('circle')
.remove();
Chaining
The return value of most selection methods is the selection itself. This means that selection methods such as .style
and .attr
can be chained. For example:
selectAll('circle')
.style('fill', '#333')
.attr('r', 20)
.on('click', function(d, i) {
select(this)
.style('fill', 'orange');
});
.each()
The .each
method lets you call a function for each element of a selection.
The callback function has two arguments usually named d
and i
. The first argument d
is the joined data (or 'datum') and will be covered in the data joins chapter. i
is the index of the element within the selection. The this
keyword refers to the current HTML or SVG element in the selection.
Here's an example where .each
is used to call a function for each of the selection's elements. The function computes whether the index is odd or even and modifies the circle accordingly:
selectAll('circle')
.each(function(d, i) {
let odd = i % 2 === 1;
select(this)
.style('fill', odd ? 'orange' : '#ddd')
.attr('r', odd ? 40 : 20);
});
Note that this
refers to the current HTML or SVG element (or the i
th element in the selection). If you wish to modify it using D3 you can select it using select(this)
.
.call()
The .call
method allows a function to be called into which the selection itself is passed as the first argument.
.call
is useful where you want a reusable function that operates on a selection.
For example, colorAll
takes a selection and sets the fill of the selection's elements to orange:
function colorAll(selection) {
selection
.style('fill', 'orange');
}
selectAll('circle')
.call(colorAll);
Filtering and sorting selections
You can filter a selection using D3's .filter
method. The first argument is a function which returns true
if the element should be included. The filtered selection is returned by the filter
method so you can continue chaining selection methods.
Thie example filters for even-numbered elements and colours them orange:
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 knowledge of data joins is helpful.
You 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 you 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
}
]
you can sort by score
using:
selectAll('.person')
.sort(function(a, b) {
return b.score - a.score;
});