D3: The Mighty Data (visualization)
Parts of this write-up come from the beautifully written scrimba course found here and also from the D3 docs
This is an intro to D3
What is D3.js?
- An Open Source JavaScript Library
- Helps creating visual representations of data
- Uses HTML, CSS, and SVG
- Viewable on modern browsers
- An easy to use API
Some examples of what D3 can be used for are:
- To generate an HTML table from an array of numbers
- To create an interactive SVG bar chart with smooth transitions and interaction
Before D3 if we wanted to, for example, change the text color of paragraph elements, it would look something like this.
var paragraphs = document.getElementsByTagName("p");
for (var i = 0; i < paragraphs.length; i++) {
paragraphs[i].style.color = 'blue';
}
But with D3, we can make the same changes with much less, and more straightforward code:
d3.selectAll("p").style("color", "white");
That would select all <p>
elements. You can also use id's, classes, attribute values, etc...
To select individual elements, rather than use .selectAll
you would just use .select
d3.select("body").style("background-color", "black");
Callback Functions
Many of the methods of D3 such as .style(), .text(), .delay(), .attr(), and many more accept a callback function as an argument.
If we want to randomly color all our paragraphs, we could do this
d3.selectAll("p").style("color", function() {
return "hsl(" + Math.random() * 360 + ",100%,50%)";
});
The callback function also accepts two arguments
function(data, index)
Let's see how we can utilize this to our advantage.
Imagine we want to alternate shades of gray for even and odd nodes:
d3.selectAll("p").style("color", function(d, i) {
return i % 2 ? "#fff" : "#eee";
});
Notice that we didn't use the d
or data argument, but we did use the i
or index argument.
Now let's use the data argument
d3.selectAll("p")
.data([4, 8, 15, 16, 23, 42])
.style("font-size", function(d) { return d + "px"; });
So the .data
information will be what gets passed through the d
argument.
This will give each paragraph a size based on the corresponding data.
Method Chaining
With D3 you can also do method chaining.
So if we had an HTML page that looked like this.
<!DOCTYPE html>
<html lang="en">
<head>
<link rel="stylesheet" href="index.css">
<title>Learn D3.js</title>
</head>
<body>
<h1>First heading</h1>
<script src="https://d3js.org/d3.v4.min.js"></script>
<script src="index.js"></script>
</body>
</html>
Then you could add the following into your JavaScript file
d3.select('body')
.selectAll('p')
.data([1,2,3,4,5])
.enter()
.append('p')
.text('D3 is awesome')
This would look like
We could try using a callback function with our data
d3.select('body')
.selectAll('p')
.data([1,2,3,4,5])
.enter()
.append('p')
.text('D3 is awesome')
.text(d => d)
Which would change our page to look like this
or even more specifically
d3.select('body')
.selectAll('p')
.data([1,2,3,4,5])
.enter()
.append('p')
.text('D3 is awesome')
.text(d => "This is number " + d)
Let's discuss what is happening in the above
.selectAll
- Selects all instances of the specified tag, class, or id
.data
- An array of the data or information to be used by callback functions
.enter
- Creates the initial join of data to elements
.append
- Specifies the type of element that should be created if there aren't enough already in existence for the each item of data
.text
- Specifies what information to put inside of the appended element.
If we were to do the same thing but not include the .enter
then it would just look like this.
This happens because our original HTML document didn't have any existing p
tags. If we excluded .enter()
but we had 2 existing p
tags then it would include the first two elements of our data. Or if we had 3 then the first three elements would show up, etc...
HOWEVER,
If we had two existing p
tags AND we still had .enter()
, then the first two items of our dataset would be ignored.
Example:
<body>
<h1>First heading</h1>
<p>Already Existing paragraph</p>
<p>Already Existing paragraph</p>
<script src="https://d3js.org/d3.v4.min.js"></script>
<script src="index.js"></script>
</body>
d3.select('body')
.selectAll('p')
.data([1,2,3,4,5])
.enter()
.append('p')
.text(d => "This is number " + d)
If you wanted to still use the .enter()
AND keep the existing p
tags with their content, you would do the following.
d3.select('body')
.selectAll('p')
.data([1,2,3,4,5], d => "This is number " + d)
.enter()
.append('p')
.text(d => "This is number " + d)
To understand better why .enter()
acts this way, you can read this
Transitions
If we wanted to use transitions to make a small change at a controlled pace, we can use d3 for that as well.
d3.select('body').transition()
.duration(2000)
.style('background-color', 'black')
Other D3 Methods
.attr - The first parameter is the attribute type we want to add, the second is whatever you want the attribute to be equal to.
- Example -
d3.select('input').attr('type', 'number')
would addtype='number'
to an<input />
.style - Set or get a CSS style property for elements in the selection.
- Example(get) -
d3.select('h1').style('color')
This would tell you the color of theh1
you selected. - Example(set) -
d3.select('h1').style('color', 'blue')
This would change the color of theh1
you selected to blue.
.duration - Specify transition duration for all elements. See above for example.
.exit - Return the selection of elements no longer associated with data.
For a full list of all the available D3 Methods, you can go here.