TUTORIAL: Simple Bar Chart in D3.js v.5

This is actually happening! I got myself together (the key is to more time is less Netflix, people) and wrote up a couple of examples in D3.js version 5 (yes, version 5!) that should get people started in the transition over to the tricky number 5. The guide assumes that you have some basics in D3 (you have an idea about SVG, DOM, HTML, and CSS), or better yet that you come from an earlier version. In this chapter we’ll create a simple bar chart. The objectives of the day are: data upload from a csv, data format setup, and drawing the data. As basic as this! Next time we will tackle scales and grids.

Make sure to check out my library for more fun examples!

Document Setup

We need something to plot our drawing onto: let’s create an html document and call it simple_graphs.html. There is a hint in the name: beware our visualisation will be very simplistic and its main goal is to explain the D3 logic, not to enchant the viewer.

Paste this into simple_graphs.html:


<!DOCTYPE html>
<head>
<meta charset="utf-8">
<title>Simple Bar Chart</title>
<script type="text/javascript" src="https://d3js.org/d3.v5.min.js"></script>
<style></style>
</head>
<body>
<div id="container" class="svg-container"></div>
<script>

//------------------------SVG PREPARATION------------------------//
var width = 960;
var height = 500;
// we are appending SVG first
var svg = d3.select("div#container").append("svg")
.attr("preserveAspectRatio", "xMinYMin meet")
.attr("viewBox", "-20 -20 " + width + " " + height)
.style("padding", 5)
.style("margin", 5)
.classed("svg-content", true);

//-----------------------DATA PREPARATION------------------------//

//---------------------------BAR CHART---------------------------//


</script>
</body>

This snippet is a simple webpage setup. In the head, we declare the document as html, give it a name, and point to the D3.js version 5 library. Document’s body starts at line 8 and as of now only includes two objects: a div element of class svg-container – that will hold our svg, and the svg appended to that div. Alternatively, you can declare an svg (like <svg width=”960″ height=”600″></svg>) and call it later. Attaching an svg to a div gives you more control over its placing, so I usually do it this way (even if rather unnecessary in our example).

The svg section holds the padding and margin information and sets the scaling of the element, so that the svg takes all available space. If you’d like to know more, I wrote about the viewBox property (and its friends) in my post Creating fun shapes in D3.js.

Data Preparation

Reading data onto the document will be our first milestone. The data set I created for this example is only two columns and 5 rows long: it holds a couple of dates with their corresponding values. The file data.csv looks like this:

date,val 10-Jul-2019,10 11-Jul-2019,20 12-Jul-2020,25 13-Jul-2020,21

Create the file data.csv in the same directory as the html and css documents.

We read the data by using the built-in d3.csv() module. Paste the following under the Data Preparation section of the html document, reload the webpage, and observe the console:


var dataset = d3.csv("data.csv");
console.log(dataset);

Congratulations! The data is now accessible to the D3 magic. The console prints the array’s information and its values:

We are promised an array of 4

You will notice that the values were recognised as strings. Our next task is to convert them to their true selves: that is, numeric and date. To do so, we need to access the data in the dataset object. We also need a custom function that will help JavaScript correctly interpret our date format. Paste the following in the Data Preparation section:


var timeConv = d3.timeParse("%d-%b-%Y");

var dataset = d3.csv("data.csv");
dataset.then(function(data) {
    data.map(function(d) {
            d.val = +d.val;
            d.date = timeConv(d.date)
            return d;});
});
console.log(dataset);

In the above example we call the dataset object and run a function on every element of it. Note the syntax of our new function: specifically, consider the role of the promise.then() method. The method can be abstracted to [promise].then(onFulfilled[, onRejected]): this is to say that then() always acts on a promise, and if such is fulfilled (e.g. there is an array of data to call), it proceeds to call a function on it. Eh? It means that our dataset array is only accessed in this step: JavaScript does not need to load it before it’s actually used. It makes the code lighter and the browser’s happier. The function also allows for specifying a response if a promise is not resolved. Take a look at my intro do Promise syntax in D3.js for some more syntax examples.

There are more fun facts about the then() method, and one of them is that then() returns a promise as well. Yes, it did it again! I promise we will come back to this promise when printing the data onto our svg.

Let’s come back to our block of code. On the successful resolution of the dataset promise we will run a function that takes one argument – that is, data. The function will load the data set in and we will access it with a super useful method called map(). The method calls a function on every element of the provided array (data). We have to be careful here as our data set contains more than one data format. We can specify how we want each column to be treated by directly accessing the column names. The values (val) will be converted to numeric, and the dates will be read with a custom made date converter, timeConv().

The timeConv function uses the built-in D3 method timeParse() and let’s us specify the date format we want to use going forward. %d stands for zero-padded day (01-31), %b is an abbreviated month name, and %Y identifies a year with century (e.g. 2019). The method is capable of dealing with a variety of date formats so feel free to experiment with it.

Save the file, reload the page, and verify the console log:

The data got converted like a charm! Note that again we are returning the dataset promise – and because we have called a function on it, it got amended! The initial file is no longer accessible!

Now we are on a straight road to success. We have the data merrily transformed and ready to use. We can proceed with plotting it! You will notice this is almost no different to the earlier versions of the D3 library.

Graph Drawing: Bar Chart

As the first plot example, we will tackle a simple bar chart. It will be so ugly that I hope it will motivate you to make it nicer in the due course.

Let’s think about what a bar chart is made of. In a nutshell is just a bunch of rectangles aligned vertically or horizontally.  Our data set will allow us to make exactly 4 bars. A rectangle is characterised by four properties: the starting point (x and y), i.e. the coords of the top left corner of each bar, the height, and the width. Let’s get to work – for starters, paste the following to the Bar Chart section:


dataset.then(function(data) {  
    svg.selectAll("div")
    .data(data)
    .enter()
        .append("rect")
    .attr("class", "bar")
    .attr("x", function (d, i) {
        for (i>0; i &lt; data.length; i++) {
            return i;
        }
    })
    .attr("y", function (d) {
        return height - d.val;
    })
    .attr("width", 40)
    .attr("height", function (d) {
        return d.val;
    });
});

Again we are calling the dataset promise, but this time the promise resolves with the data we modified in the previous section. On a successful fulfillment, we call a function that builds a bar chart. The function definition should be something you are used to from the previous D3 versions, but let’s summarise it:

The function first selects all empty div elements from the svg (of which there are infinite; with this we allow each rectangle to be appended to a separate div later), calls our promised (and delivered) data, and appends as many rectangles as there are data array elements. We then proceed to specifying the properties I mentioned earlier.

Attribute x is each rectangle’s starting point on the horizontal axis. Since each bar will display a value for a certain date, our dates are the ones plotted on the x axis. But they are no good! Dates are meaningless in a context of a 2D plane: we need a numeric coordinate. Instead of dates, we will use their indices to locate the bars on the axisg. See how i in function(d,i) stands for index.

Attribute y decides the starting point of each bar on the vertical axis. I envisioned my graph with bars aligned in their base line. Remember that the top left corner of an svg is set at point (0,0) and it’s left max is at (svg height,0). To plot a rectangle, we specify its top left position. To achieve my design, we need to start plotting the rectangles at (height – val, x) point. In layman terms, if our svg’s height is y = 10 pixels and the bar’s height is y = 8 pixels, we start drawing the bar at y = 2 pixels so that the line of 8 pixels is drawn downwards from that position and finishes correctly at the svg’s height.

After those mind boggling coordinate calculations, the only properties that are unset are the rectangle’s height and width. We decide a common width for all bars, and the height is set at the data value that we want to visualise.

All is set*: save the file and observe the webpage.

*I made a sneaky mistake in specifying one of the attributes. Try to catch it before reloading the page!

Where are you, oh rectangles?

How disappointing! All this work to create a sad blob of black ink.

I suggest you try fixing the code on your own before reading on.

The issue has been caused by our good intentions: remember that our x attribute is set to the row indices instead of dates. That means the first bar is plotted at x = 0px, and the following bar at x = 1px. Since our bar width is set to 40 pixels, those poor rectangles sit on top of each other. Let’s separate them. And while at it, let’s adjust the size of the graph so the rectangles are prominent and strong (like this data actually had a meaning).

Replace the previous code with the following snippet:


dataset.then(function(data) {  
    svg.selectAll("div")
    .data(data)
    .enter()
        .append("rect")
    .attr("class", "bar")
    .attr("x", function (d, i) {
        for (i>0; i &lt; data.length; i++) {
            return i * 41;
        }
    })
    .attr("y", function (d) {
        return height - d.val*10;
    })
    .attr("width", 20)
    .attr("height", function (d) {
        return d.val*10;
    });
});

Note how I multiplied the indices by 41 so that the first bar is still printed at 0px, but the following rectangle starts at 1*41, so 41px, and the next one at 2*41, so 82px. I also stretched the bars by multiplying their height by 10. 

The rectangles appear

Here it is in its full beauty!

Let me know in the comments how you got on and if you enjoyed the tutorial. In the next post I will show you how to add scales and axes to our visualisation. 

Here’s the full code:


<!DOCTYPE html>
<head>
<meta charset="utf-8">
<title>Simple Bar Chart</title>
<script type="text/javascript" src="https://d3js.org/d3.v5.min.js"></script>
<style></style>
</head>
<body>
<div id="container" class="svg-container"></div>
<script>

//------------------------SVG PREPARATION------------------------//
var width = 960;
var height = 500;
// we are appending SVG first
var svg = d3.select("div#container").append("svg")
.attr("preserveAspectRatio", "xMinYMin meet")
.attr("viewBox", "-20 -20 " + width + " " + height)
.style("padding", 5)
.style("margin", 5)
.classed("svg-content", true);

//-----------------------DATA PREPARATION------------------------//
var timeConv = d3.timeParse("%d-%b-%Y");

var dataset = d3.csv("data.csv");
dataset.then(function(data) {
    data.map(function(d) {
            d.val = +d.val;
            d.date = timeConv(d.date)
            return d;});
});
console.log(dataset);

//---------------------------BAR CHART---------------------------//
dataset.then(function(data) {  
    svg.selectAll("div")
    .data(data)
    .enter()
        .append("rect")
    .attr("class", "bar")
    .attr("x", function (d, i) {
        for (i>0; i < data.length; i++) {
            return i * 41;
        }
    })
    .attr("y", function (d) {
        return height - d.val*10;
    })
    .attr("width", 20)
    .attr("height", function (d) {
        return d.val*10;
    });
});

</script>
</body>

Follow me on Twitter for more hot D3.js content:

Leave a Reply