How to create a grouped bar chart in D3.js

Vaibhav Kumar
4 min readJul 9, 2018

Linkedin : https://www.linkedin.com/in/vaibhav-kumar-4794b389

D3.js is an open source library that attaches your data to DOM (Document Object Model) elements. Like a cherry on top of the cake makes the cake look good similarly CSS3, HTML is used for cosmetic work on data. Finally, you can make the data interactive through the use of D3.js data-driven transformations and transitions.

In this tutorial, we are going to create a grouped bar chart displaying the accuracy score of two machine learning models. We will be pulling in data from an external API and rendering a grouped bar chart with labels and an axis inside the DOM.

For those who directly want to jump to code i have added the JS fiddle link to the bottom of this post.

Getting started

First of all, we will import the D3.js library directly from the CDN inside our HTML. We have also added an <div> tag inside our HTML to create the bar chart inside it using D3.js

<html>
<head>
<link rel="stylesheet" href="index.css">
</head>
<body>
<div id='d3Id'></div>
<script src="https://d3js.org/d3.v4.min.js"></script></body>
</html>

Getting the data

However, most of the blogs on the internet are about reading the CSV/TSV files and then plotting the data but here this post focuses on REST responses.

field1 and field2 here represents the accuracy score of the model

The response format from a REST API can be of any format but preferably it should be of an array of dictionary format.

public createTodo(todo: Todo): Observable<Todo> {
return this.http
.post(API_URL + '/todos', todo)
.map(response => {
this.models=response
})
.catch(this.handleError);
}
..
..
..
var models = [
{
"model_name":"f1",
"field1":19,
"field2":83
},
{
"model_name":"f2",
"field1":67,
"field2":93
},
{
"model_name":"f3",
"field1":10,
"field2":56
},
];

Select SVG element

Once we have our data ready we will use select the SVG element and put some styling to it. In the code below, we are setting the width and height of the SVG container by first selecting it using the select()method and then using the attr()method to assign the attributes.

We have also defined margins and are using their values in calculating the width and height attributes of the SVG container. These margin values will help us later in positioning and display our chart correctly.

There is no external CSS used here in the example but we can use it according to our needs.

var container = d3.select('#d3Id'),
width = 520,
height = 220,
margin = {top: 30, right: 20, bottom: 30, left: 50},
barPadding = .2,
axisTicks = {qty: 5, outerSize: 0, dateFormat: '%m-%d'};
var svg = container
.append("svg")
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform", `translate(${margin.left},${margin.top})`);

Add Scales

Now, we want to add scales to our chart. As we know, our data consists of model names, Therefore, we can assume that the x-axis would contain the model names in the string format and y-axis would contain accuracy values.

var xScale0 = d3.scaleBand().range([0, width - margin.left - margin.right]).padding(barPadding)var xScale1 = d3.scaleBand()var yScale = d3.scaleLinear().range([height - margin.top - margin.bottom, 0])

And so, in the code snippet above, we created a scale of linear type on y-axis and a band on x-axis. We are also providing these scales with the ranges as per the width and height of our SVG container.

Adding Axis

We are now going to add our left and bottom axes inside our svg element for the bar chart. The left axis will represent the accuracy value while the bottom axis displays the model name.

var xAxis = d3.axisBottom(xScale0).tickSizeOuter(axisTicks.outerSize);
var yAxis = d3.axisLeft(yScale).ticks(axisTicks.qty).tickSizeOuter(axisTicks.outerSize);

Mapping the Axis with data

Here we will be mapping our x-axis with the model’s name and y-axis with the maximum range of accuracy value. It is kind of dynamic in nature as many values it will find for the model name it will keep on mapping these value on x- axis.

xScale0.domain(models.map(d => d.model_name))xScale1.domain(['field1', 'field2']).range([0, xScale0.bandwidth()])yScale.domain([0, d3.max(models, d => d.field1 > d.field2 ? d.field1 : d.field2)])

Create and Transform Group Element

Grouping elements help organise similar or related elements together. Here, after rendering a group element, we provide it with some transformation.

D3 gives us various options to transform our elements. In the code above, we are using the translate property to reposition our group element with margins on its left and top.

var model_name = svg.selectAll(".model_name")
.data(models)
.enter().append("g")
.attr("class", "model_name")
.attr("transform", d => `translate(${xScale0(d.model_name)},0)`);

Appending the bars

After all the transformations we will be adding the bars to the graph along with the coloring attributes.

/* Add field1 bars */
model_name.selectAll(".bar.field1")
.data(d => [d])
.enter()
.append("rect")
.attr("class", "bar field1")
.style("fill","blue")
.attr("x", d => xScale1('field1'))
.attr("y", d => yScale(d.field1))
.attr("width", xScale1.bandwidth())
.attr("height", d => {
return height - margin.top - margin.bottom - yScale(d.field1)
});

/* Add field2 bars */
model_name.selectAll(".bar.field2")
.data(d => [d])
.enter()
.append("rect")
.attr("class", "bar field2")
.style("fill","red")
.attr("x", d => xScale1('field2'))
.attr("y", d => yScale(d.field2))
.attr("width", xScale1.bandwidth())
.attr("height", d => {
return height - margin.top - margin.bottom - yScale(d.field2)
});

Adding X-axis and Y-axis

As soon we have added the bars we will be adding the axis that we have created above to the rest of the elements of graphs.

// Add the X Axis
svg.append("g")
.attr("class", "x axis")
.attr("transform", `translate(0,${height - margin.top margin.bottom})`)
.call(xAxis);
// Add the Y Axis
svg.append("g")
.attr("class", "y axis")
.call(yAxis);

Here is the final result: Grouped Bar chart

Here is the closest i can get with the code in JS fiddle

Feedback and questions are more then welcome!

--

--

Vaibhav Kumar

Bloggger |Freelancer |Data enthusiast | Techie | Bike ride is Love