When it comes to visualizing data, there are quite a few libraries out there. The level of abstraction they introduce varies. Libraries like C3 and Recharts depend on the Data-Driven Documents library – D3. Learning D3 might mean dealing with more code. The experience you can get this way will benefit you though so that you can make better use of libraries that depend on D3 under the hood. It makes it worthwhile to learn the basics first.
Introduction to the Data-Driven Documents
The D3 library helps to visualize data using SVG, Canvas, and HTML. It aims to be low-level and therefore, flexible. It has a lot of DOM-managing functionalities built-in.
We install the D3 library from npm and run the application using Webpack.
1 |
npm install d3 |
If you want to know more about Webpack, check out the Webpack 4 course
1 2 3 |
import { select } from 'd3'; select('h1').style('color', 'red'); |
The code above selects the first element that matches the h1 selector. When we find it, we can alter it in various ways. D3 has a set of methods meant for modifying selected nodes. Above we use the style function that aims to change the color of the h1 element.
There are more methods like attr, property, or text. If you want to experiment with them, you can go to the d3js.org website and use the global d3 object.
Aside from using the select function, you can also use selectAll. It might come in handy when you want to modify multiple elements at once.
1 2 3 4 5 6 7 8 |
import { selectAll, select } from 'd3'; const h1 = selectAll('h1'); h1.each(function() { select(this) .style('color', 'red'); }); |
In the code above, we invoke a function for every h1 element that we find wit the use of the each method. In its context, the “this” keyword points to a current element. Here we can see that the select function also accepts a DOM node instead of a selector. Thanks to that we can call the style function on every h1 element.
We can make the code above even better. In fact, we don’t have to call the each method!
1 2 3 4 5 |
import { selectAll } from 'd3'; const h1 = selectAll('h1'); h1.style('color', 'red'); |
The selectAll function wraps all elements in the page in a way that you can interact with them all at once.
Processing data
To create some more useful things, we need to get into the data binding, also called joining. To understand this concept, let’s look at this data processing:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
import { select } from 'd3'; const data = [10, 25, 40, 75]; const svgHeight = 100; const svg = select('body').append('svg') .style('background-color', 'lightgray') .attr('height', svgHeight); const barWidth = 20; data.forEach((value, index) => { svg.append('rect') .attr('height', value) .attr('width', barWidth) .attr('x', index * barWidth) .attr('y', svgHeight - value) }); |
In the example above, we use the append function to create one SVG element and multiple rectangles. We put every bar to the bottom of the SVG element using attr('y', svgHeight - value) – this is because if we want to put some element lower, we need to increase the “y” coordinate.
Thanks to the above, we manage to create an uncomplicated bar chart:
Data binding
The above is not the best way to achieve this. Let’s improve it with the use of the data binding.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
import { select } from 'd3'; const values = [10, 25, 40, 75]; const svgHeight = 100; const svg = select('body').append('svg') .style('background-color', 'lightgray') .attr('height', svgHeight); const barWidth = 20; svg.selectAll('rect') .data(values) .enter() .append('rect') .attr('height', value => value) .attr('width', barWidth) .attr('x', (value, index) => index * barWidth) .attr('y', value => svgHeight - value); |
Let’s break it apart, step by step.
First, you might find it surprising that we call svg.selectAll('rect') even though there are no rectangles yet. This approach aims to utilize the declarative style of programming. You can think of calling the selectAll function like the above as making the holes in our space that we aim to fill later. It is yet unclear how many elements we intend to put there.
The crucial part comes with the data(values). With this, we declare the relationship between the data and the type of DOM element that we specified with selectAll('rect').
The enter() function creates missing elements that correspond with our data. It determines how much elements we intend to create.
The final touch is the use of the append('rect') function that fills the DOM tree with desired elements, and now we can style them as we desire. Since we use the selectAll function, the attr() applies to every rectangle. When we pass a function to it, we can use the data associated with a particular rectangle like that: attr('x', (value, index) => index * barWidth).
Thanks to all that we’ve reproduced the same outcome with the use of the data binding.
Summary
In this article, we’ve gone through the very basics of D3. This includes the fundamentals of using selectors and how to modify the nodes in the DOM tree. We’ve also created a foundation on which a bar chart can be created upon. To do this, we’ve used SVG and touched the topic of the coordinates. To render the chart properly, we’ve introduced the concept of data binding instead of using a simple for loop. All of the above a good starting point when learning the Data-Driven Documents library.