Data Visualization with D3.js

July 9, 2020

D3 (Data-Driven Documents) is a JavaScript library that allows us to manipulate documents based on data. With D3, we can bind data to a DOM element and then apply data-driven transformations to the document. For example, we can create an HTML table from an array of numbers, or create an interactive SVG bar chart.

intro
You don’t need much background knowledge about SVG but feel free to check out the resources listed at the end of this article. To run the examples in this article or play with D3.js, you can use blockbuilder.org or Observable, where you can also view others’ work and get inspiration.

To use D3 in your local environment, install or insert this snippet:

<script src="https://d3js.org/d3.v5.min.js"></script>

Let’s dive in!!

Selections

<svg>
<rect />
<rect />		// Three Rectangle Elements
<rect />
</svg>
<script>
var data = [100, 250, 175];
var rectWidth = 100 //The width of the bar in a bar graph
var height =200;
d3.selectAll('rect')
	.data(data) // loops through each svg element and sets __data_ attribute
	.attr('x', (d, i) => i * rectWidth) // function is set the x coordinate of each bar, index*width of bar
	.attr('y', d => height - d) // d is the bound data
	.attr('width', rectWidth)
	.attr('height', d => d)
	.attr('fill', 'blue')
	.attr('stroke', '#fff');
</script>

This produces a bar chart:

bar chart d3 js

d3.selectAll('rect') is a selector, which goes and selects all the “rect” elements in the document. Modifying documents using DOM API is tedious and repetetive. With d3.selectAll(), we can use any CSS selector to select everything on the DOM.

Under the hood, d3.selection is an array of all the DOM elements wrapped around a powerful API which can be used to set attributes, styles, properties, data and more. This has a .data() function which is used to bind the data to the selections. You can pass an array of data, d3 maps the data one-to-one to the array of selections. The various attributes in the above example are x axis, y axis, width of the bar chart, color and style. d3 loops through each element and returns the value from the function based on the data you passed in.

Enter-Exit

When joining elements to data by key, there are three possible logical outcomes:

  • Update - There was a matching element for a given datum.
  • Enter - There was no matching element for a given datum.
  • Exit - There was no matching datum for a given element.

In the previous example, we saw that we had to have n number of <rect/> elements for n number of elements of data. Using D3’s enter and exit, we can create new nodes for incoming data and remove nodes that are no longer needed.

<svg></svg>
<script>
var rectWidth = 100;
var height = 300;
var data = [100, 250, 175, 200, 120];

var svg = d3.select('svg');
svg.selectAll('rect')
.data(data)
.enter().append('rect')
.attr('x', (d, i) => i * rectWidth)
.attr('y', d => height - d)
.attr('width', rectWidth)
.attr('height', d => d)
.attr('fill', function(d){
	if (d === 250){
		return "blue";
	}
	else{
		return "red";
	}
})
.attr('stroke', '#fff');
</script>

d3 js enter exit
We have an empty selection svg to which we are creating a rect element and appending it to the DOM for every iteration through the data. This can be very useful if the data is changing because we can change different properties based on data by writing functions in the .attr() functions.

If we simply want to update a DOM element rather than adding new elements, we do this by:

var p = d3.select("body")
  .selectAll("p")
  .data([4, 8, 15, 16, 23, 42])
    .text(function(d) { return d; });

Exit selections are used when the data is less than the number of DOM elements. For example, if the number of elements in the array is less than the number of rect elements in the DOM, then we can perform operations on those excess elements, such as removing them.

d3.select('#content')
	.selectAll('div')
	.data(myData)
	.exit()
	.remove();

Transitions

Without Transitions

d3 js without transitions
With Transitions

ds js with transitions
A transition is an interface for animating changes to the DOM. Instead of applying changes instantaneously, transitions interpolate smoothly over time, from a state-A to the desired state-B.

If we want the result to be immediate then we can do :

d3.select("body").style("color", "red");

To, instead, animate the change over time, derive a transition:

d3.select("body").transition().style("color", "red");

We will discuss one way of doing transitions in this article.

var t = d3.transition().duration(1000); // we can declare a constant or function anywhere
var svg = d3.select('svg');
	var bars = svg.selectAll('rect') .data(data, d => d);
	// exit
	bars.exit()
		.transition(t)
		.attr('y', height)
		.attr('height', 0)
		.remove();
// enter
	var enter = bars.enter()
				.append('rect')
				.attr('width', rectWidth)
				.attr('stroke', '#fff')
				.attr('y', height);
// enter + update
bars = enter.merge(bars)
			.attr('x', (d, i) => i * rectWidth)
			.attr('fill', d => colors(d))
			.transition(t) .attr('y', d => height - d)
			.attr('height', d => d);

In the example above, we see a transition is applied to .exit() selection. Tt returns a selection of DOM elements, to which the target state is height = 0 it is applied in a smooth, animated way by using transitions. Everything after .transition() is the target state. The from state, or the initial state, is the DOM if no attributes are given. In the example above, we gave certain attributes to the enter + update which is the initial state. You can check out the transition API here.

D3 API offers many functionalities like Shapes, Axis, Geo-Projection, Forces and many more. Check them out if you are interested.

Resources and References


About the author

Rohan Reddy

Rohan Reddy is an undergraduate student at the University of Hyderabad pursuing a degree in Computer Science. Rohan is particularly interested in machine learning and web development.

This article was contributed by a student member of Section's Engineering Education Program. Please report any errors or innaccuracies to enged@section.io.