How to Improve D3.js Graphs with Annotations
By: Angelica Lo Duca
Tags: Data Visualization, Graph, JavaScript, Data Science, D3js

Data Visualisation

How to Improve D3.js Graphs with Annotations

Image by Author

The Data Driven Library (D3) is one of the most famous Javascript libraries used to build graphs and draw paintings. Regarding graphs, there are many web sites providing ready-to-use code, such as the D3 Graph Gallery and From Data to Viz. However, these web sites provide only the basic code, without tips or details to highlight data context or insights.

In this tutorial, I exploit the d3-annotation library to extend two simple graphs provided by the D3 Graph Gallery, in order to make them more readable from general audience:

a simple bar chart

a simple line chart

No one ever made a decision because of a number. They need a story. (Dan Kahneman)

All the code is available in my GitHub repository.

Bar Chart

The first example is the simple bar chart, provided by the D3 Graph Gallery and shown in the following figure:

Image by Author

The graph tries to plot the following dataset:

Image by Author

From the point of view of data context, this graph presents at least two problems:

countries in the bar are not ordered, thus it is difficult to understand which country behaves better than the others.

there is no focus on the graph, thus the reader should imagine what the graph wants to say.

In order to improve the graph, I deal with the two problems separately.

Regarding the order of the bar, the graph can be improved by ordering all the results into a descending order according to the value field. In D3 this can be achieved through the sort function:

data.sort(function(x, y){return d3.descending(x.Value, y.Value);})

This function requires as input parameter a function which specifies the rule used to order every pair of fields.

Regarding the fact that the graph has no focus, I can focus on a single country, such as France, and represent it with a different color with respect to the other countries. In addition, I can change the color of the other countries to a tonality of grey, in order to reduce the noise produced by the other countries. This can be achieved by specifying a specific color for the rectangle generated for each country:

svg.selectAll("myRect")

// other code

.attr("fill", function(d){ if (d.Country == 'France') return "#cc0000" ; else return "#a3a3c2"})

The described effects provide the following graph:

Image by Author

Now, I can imagine to describe why France has reached the third position in the ranking. Maybe it is due to its strong marketing policy in the last year. Thus I can add an annotation to the graph, explaining the context.

Firstly I build the annotation, by specifying the text (in the note field), the color and the position (through the x, y, dx and dy keys):

const annotations = [

{

note: {

label: "Thanks to its marketing policy, in 2021 France has reached the third position.",

title: "France product sales",

wrap: 200, // try something smaller to see text split in several lines

padding: 10 // More = text lower



},

color: ["#cc0000"],

x: x(2500),

y: 100,

dy: 100,

dx: 100

}

And then I append the annotation to the SVG graph:

const makeAnnotations = d3.annotation()

.annotations(annotations) svg.append("g")

.call(makeAnnotations)

The following figure shows the final result:

Image by Author

Line Chart

The first example is the simple line chart, provided by the D3 Graph Gallery and shown in the following figure:

Image by Author

This line presents mainly two problems:

The y axis does not have a label thus making it difficult to understand the unit of measure.

Similarly to the previous graph, there is no context.

Regarding the y-axis label, it is simple to add it. I add some text (e.g. Earnings) to the SVG image, I rotate it 90 degrees and I put near the y axis:

svg.append("text")

.attr("transform", "rotate(-90)")

.attr("y", 0 - margin.left)

.attr("x",0 - (height / 2))

.attr("dy", "1em")

.style("text-anchor", "middle")

.text("Earnings");

Regarding the context, it depends on the data. I can imagine that the data refer to France’s earnings over a given period of time. I note that there is a flop on Wed 18th, while from Friday 20th data rise again. Thus I can highlight these two aspects, with two notes.

The first note should highlight all the flopping period, thus I can use a d3.annotationCalloutCircle , while the second one can highlight only one point, in correspondence of Friday 20th, thus I can use a d3.annotationCalloutElbow .

Here is the code to build the annotations:

var parseDate = function(d){ return d3.timeParse("%Y-%m-%d")(d)}

const annotations = [

// first annotation

{

note: {

label: "Earnings plummeted",

title: "April 17th - 19th",

wrap: 150, // try something smaller to see text split in several lines

padding: 10 // More = text lower



},

color: ["#cc0000"],

x: x(parseDate('2018-04-18')),

y: y(8197),

dy: -100,

dx: -5,

subject: {

radius: 50,

radiusPadding: 5

},

type: d3.annotationCalloutCircle,

},

// second annotation

{

note: {

label: "Strong Recovery",

title: "April 20th",

wrap: 150, // try something smaller to see text split in several lines

padding: 10 // More = text lower



},

color: [" #00b300"],

x: x(parseDate('2018-04-20')),

y: y(8880.23),

dy: 40,

dx: 40,

type: d3.annotationCalloutElbow,

},



]



window.makeAnnotations = d3.annotation()

.annotations(annotations)



svg.append("g")

.call(makeAnnotations)

I have defined a variable parseDate which parses a string and returns a date.

The following figure shows the final graph:

Image by Author

Summary

In this short tutorial, I have illustrated how to improve a bar chart and a line chart in order to provide some context. I have exploited the d3-annotation library, which is a third-part library for annotations in d3.

In the next days I’m going to write a tutorial for the improvement of geographical maps…thus stay tuned ;)

If you wanted to be updated on my research and other activities, you can follow me on Twitter, Youtube and and Github.