These pages link to here: spiderweb.html
Table of contents
D3 is a data visualisation library for HTML, SVGs, and CSS.
Generally, it works by:
- Selecting DOM elements
- Joining data to those elements
- Adding/removing elements from the page to match the data you've joined with
- Transforming the elements
Along with that, D3 also includes functions that let you easily
- Scale input values to match some scale, useful when making axes
- Make shapes
- Layout data in more complicated ways
- Interact with your data or elements
everything here will call functions like d3.select()
, not select()
API reference#
Normally (but not always), D3's methods return DOM elements, so you can chain them together:
d3.select("#treeSvg g.tree__nodes")
.selectAll("circle.tree__node")
.data(root.descendants())
.enter()
.append("g");
Selections#
First, you need to select
HTML or SVG elements on the page - D3 works with both HTML and SVG - using CSS selector syntax:
-
select
selects 1 element, -
selectAll
selects all elements that match the selector
d3.select("body").style("background-color", "black"); // selects just the <body>, and makes it black
d3.selectAll(".paragraph").style("color", "blue"); // selects all .paragraph's, and makes them all have blue text
Normally, you'll want to join your elements with some data afterwards:
Data joins#
You then attach/bind some data to your elements after you've selected them - the data must always be an array.
For example, this selects all the circles, then sets their radiuses to be each score:
<circle r="40" cx="100 />
<circle r="40" cx="120" />
const scores = [
{
"name": "Andy",
"score": 25
},
{
"name": "Beth",
"score": 39
}
];
d3.selectAll('circle')
.data(scores)
.attr(
'r',
function(d, i) {
return d.score;
}
);
d3.attr()
sets an attribute on each circle
, here the radius.
The function here is called once for each element in scores
, and implicitly passes in each score
object, and its index - 0 to 5 here.
When D3 joins data to elements, it adds a _data_
attribute to each DOM element with the joined data.
Joining 1 thing#
You can also join a single thing (like 1 objet by itself), with .datum()
:
const featureCollection = {type: 'FeatureCollection', features: features};
d3.select('path#my-map')
.datum(featureCollection);
If the data's not exactly the right length?#
The previous examples assume that your data is exactly the right length to fit whatever DOM elements you already have on the page - if there aren't enough (e.g. if there aren't any), or there are too many, matching your selection, you'll need to use enter() or exit()
Enter/exit#
If there aren't enough DOM elements matching selection (e.g. if there aren't any), or there are too many, you need to use D3's enter() or exit() methods.
Enter()#
d3.enter()
returns placeholder nodes for each DOM element in a selection after joining data to the selection, filling in any missing ones - so, if there were 5 things in the data array, but no DOM elements matching the selection, it'll create 5 dummy nodes, if there's 1 element it'll make 4 nodes, etc.
What it returns is called the "enter selection" - you can also think of it as placeholder nodes for each datum that had no corresponding DOM element in the selection.
After entering, you'll normally want to call d3.append()
to actually append the new nodes to the page; enter()
doesn't actually create new elements by itself.
This will add 6 new DIVs to the body:
const div = d3.select("body")
.selectAll("div")
.data([4, 8, 15, 16, 23, 42])
.enter()
.append("div")
.text(d => d);
// result:
<div>4</div>
<div>8</div>
<div>15</div>
<div>16</div>
<div>23</div>
<div>42</div>
Exit()#
Similarly, exit()
and remove()
do the reverse:
exit()
returns the "exit selection", the DOM elements that need to be removed for the earlier selection to match the data - you can also think of it as DOM elements which don't have a matching thing in the data.
Taking the same DOM elements as above,
const div = d3.select("body")
.selectAll("div")
.data([1, 2, 4, 8, 16, 32], d => d);
Since a key function was specified (as the identity function), and the new data contains the numbers [4, 8, 16] which match existing elements in the document, the update selection contains three DIV elements. Leaving those elements as-is, we can append new elements for [1, 2, 32] using the enter selection:
div.enter().append("div").text(d => d);
// to remove the exiting elements [15, 23, 42]:
div.exit().remove();
// result:
<div>1</div>
<div>2</div>
<div>4</div>
<div>8</div>
<div>16</div>
<div>32</div>
order()#
You might need to call selection.order()
to re-order data.
merge()#
selection.merge(otherSelection)
merges two different selections together
join()#
There's also the selection.join()
method that's shorthand for calling selection.enter
, selection.exit
, selection.append
, selection.remove
, and selection.order
- it appends, removes and reorders elements to match a previous d3.data()
:
svg.selectAll("circle")
.data(data)
.join("circle")
.attr("fill", "none")
.attr("stroke", "black");
// is equivalent to
svg.selectAll("circle")
.data(data)
.join(
enter => enter.append("circle"),
update => update,
exit => exit.remove()
)
.attr("fill", "none")
.attr("stroke", "black");
Transformations#
See the API reference.
A lot of the time, you'll want to use
-
d3.attr()
to set an attribute on an element -
d3.style()
to set a style -
d3.classed()
to add or remove a class -
d3.property()
to set a property, likechecked
-
d3.text()
to set the text content -
d3.html()
to set the HTML content
Each method returns the selection again, so you can chain them together:
d3.selectAll('circle')
.data(myData)
.attr('r', function(d, i) {
return d;
})
.classed('high', function(d, i) {
return d >= 40;
})
.attr('cx', function(d, i) {
return i * 120;
});
Examples#
Simple bar chart#
From D3 in depth,
const cities = [
{ name: 'London', population: 8674000},
{ name: 'New York', population: 8406000},
{ name: 'Sydney', population: 4293000},
{ name: 'Paris', population: 2244000},
{ name: 'Beijing', population: 11510000}
];
// Join cities to rect elements and modify height, width and position
d3.selectAll('rect')
.data(cities)
.attr('height', 19)
.attr('width', function(d) {
const scaleFactor = 0.00004;
return d.population * scaleFactor;
})
.attr('y', function(d, i) {
return i * 20;
});
// Join cities to text elements and modify content and position
d3.selectAll('text')
.data(cities)
.attr('y', function(d, i) {
return i * 20 + 13;
})
.attr('x', -4)
.text(function(d) {
return d.name;
});
Useful functions#
Scaling inputs#
Making shapes#
Layouts#
Interactions#
Resources
D3 in depth
Interactive data visualisation for the web