d3 grouped bar chart
D3 is a very robust library that will help you create a great spectre of visualizations, if you don’t know where to start you should take a look to the gallery. Stackoverflow has a D3 tag and in the last couple months I was able to answer three questions regarding Grouped Bar charts. In this post I will try to explain as best as possible how to create a Grouped Bar chart from the ground up.
Data visualization is one of the main jobs I have at my workplace, if you’ve never worked with data visualizations you should try it, it’s kinda “soothing”. In the beginning when we needed simple charts we opted to use libraries such as Highcharts and C3.js, as we developed more complex UIs we ended up trying out D3.js.
To make things a little more visual, code snippets will be followed by the result of the given code via jsFiddle. The full code will be using webpack as asset building tool, you can find the full source code here.
Movie ratings are perfect for grouped bar charts, each movie has a different rating from a wide pool of critics. Comparing the ratings should be simple, we will be using a simple dataset with the following object structure:
We have an array of movie objects, each object has an array of values which contains the three ratings taken from Rotten Tomatoes. Each of the ratings will be inside the [0, 100] range, 0 being the lowest possible rating and 100 being the highest possible one. The all rating corresponds to the whole group of critics, top just takes into account the top critics and audience is the rating that the audience grants to the movie. The chart will group the ratings by movie and display them in a simple way so the information can be understood really quick. In the end we will have a chart like the one below.
That being said, lets begin, first we will need a simple HTML layout so we can start developing our project:
We will be using ES6 and the new modular d3 which lets us import just the necessary modules from the library. At this moment we just need to render a plain
svg into the DOM, importing the
select library will allow us to transform the DOM. To understand a little bit more lets dig into the selection library API docs, specially into the “Selecting Elements“ paragraph.
Selection methods accept W3C selector strings such as
.fancyto select elements with the class fancy, or
divto select DIV elements. Selection methods come in two forms:
selectAll: the former selects only the first matching element, while the latter selects all matching elements in document order.
In the following snippet we are creating just the basic stuff we will need for our chart:
- Importing select library from the d3 package.
- Define the SVG dimensions.
- Use the select library to transform the DOM by selecting our node and appending a new SVG one with certain modifications.
The jsFiddle shows us how we can transform the DOM with the select library API. We are using some methods provided by the select library,
attr. The first method will take as argument a string and will append the tag into the selected node, in the jsFiddle you will be able to check that our
#grouped-bar element has a new node, and that node is a SVG. If you check even further in the SVG tag you will find that some attributes were defined, those attributes were the ones we defined in our transformation by using the
Now that we have a SVG in which we can render our information, lets add a
g element into our SVG node. This element will contain all the elements that we will build in our chart. We will also need a way to color our bars in the near future so lets also add an object that will contain the mappings of our colors to each given rating. Finally, lets make a request to obtain the dataset that we will be using in this example.
Right now we have a SVG with a
g element which has
rect as child node. We transformed our
g tag a little, we gave it a class name
.grouped-bars-main-group and made a simple transformation. The
g tag was translated in the x position by the left margin variable and in the y position by the top margin variable. This will make every new node appended into this element to start at the 0 , 0 position of the group element, and since the group element was translated all our child nodes will be restricted by the margin we provided. The
rect inside the group element is a clear example of this, we just appended the element into our group element and since it will start rendering from the initial position of the parent group it will be respecting the SVG margin we declared.
Ok, we have an SVG and a group element, so what should we do next?. We need to declare scales in order to render our dataset in a proper way. D3 provides functionality to create certain types of scales, we will need 3 scales to accomplish our chart. Below is a simple sketch of the scales we should be needing.
The first scale we will check is the
scaleBand, this scale is part of the ordinal scales that d3 offers. From the d3 docs we can find this definition:
Unlike continuous scales, ordinal scales have a discrete domain and range. For example, an ordinal scale might map a set of named categories to a set of colors, or determine the horizontal positions of columns in a column chart
This definition fits right into our use case, we need to map a set of categories to the correct position in our x axis. The
scaleBand has a more specific definition:
Band scales are like ordinal scales except the output range is continuous and numeric. Discrete output values are automatically computed by the scale by dividing the continuous range into uniform bands.
The second scale we will review is the linear scale:
Constructs a new continuous scale with the unit domain [0, 1], the unit range [0, 1], the default interpolator and clamping disabled. Linear scales are a good default choice for continuous quantitative data because they preserve proportional differences. Each range value y can be expressed as a function of the domain value x: y = mx + b.
This type of scale will be useful to map our rating’s values, we want to get a rating and output a value within the range we provide. Now lets start constructing our scales.
We created a
xScale of type
scaleBand, the purpose of using this type of scale is to correctly map our movie names to the corresponding x position. The
domain of the
xScale will be all our movie names, the range will be defined by the width of our SVG. Remember the definition given by the d3 API docs? well, our
xScale will help us compute the uniform bands we need to segment our
x axis. So, lets see what we can do with the
xScale correctly maps our movie names! You can see how calling our scale with Sausage Party returns 0, Suicide Squad returns 91.6 and so on. This is great, the scale will correctly return the x position in which our movie should render its information. When we try to map a value that doesn’t exist in our
xScale we will receive
undefined as the return value. Finally, if we want to know the width of the uniform bands of our scale we can simple call the
bandwidth in our
The second scale we created is the
yScale, this one will map our rating’s values (domain [0, 100]) to the range we provided (SVG height) and if we give it any input within the specified domain we should be able to get the y value as the output.
The last scale (
ratingsScale) is the one that will allow us to map our rating names to the corresponding x inside our movie’s elements. This scale will take as
domain the types of ratings we have for each of our movies and for range the width of the uniform band of our
Axis make our chart information more readable, they help us understand the relationship between the data we are representing. We will need two
axis in our chart, the x (Movies) and the y (Movie Rating Score). D3 has utilities that will reduce the hassle of creating all the logic behind the
axis, by calling any of the
axis provided by d3 (
axisLeft) we will be able to render human-readable reference marks for our scales.
The movies axis will be our
xAxis, using the
axisBottom with our
xScale as argument we will construct a new bottom-oriented
axis with the movie names drawn below the horizontal
The rating’s axis will be our
yAxis, using the
axisLeft with our
yScale as argument we will construct a new left-oriented
axis with the rating’s values drawn to the left of the
Grid lines are an easy way to grasp really quick the values shown in charts, it’s a great visual aid. We will create a simple set of grid lines for our rating’s values. We will repeat the process of creating an
axisLeft with the
yScale as argument, but this time we will add the
tickSize into the definition of this
axis. Adding the
tickSize will grant us the ability of modifying the size of the ticks rendered, so lets set the size to the width of the chart.
scales are correctly defined, the question is, how are we going to use them to render our information?. Remember the
main variable we defined?, that variable has the reference to the
g element with the margin we had defined previously, in this node is where we will append all our new nodes.
SVG elements do not support a
z-index or similar property, everything is rendered in order one element above the other. We want our grid lines to be behind our bars and
axis, so we have two options, append our elements in the order we want (the method we will use) or make use of the
insert utility d3 provides. D3 also offers the
call function which takes a selection as input and hands that selection off to any function, we will make use of this utility to create all our
axis by giving the functions our newly appended
g elements as inputs and as a functions ours
Labels will greatly increase the way users understand the chart, they will help to recognize what is the category of each of the ratings the movie has. Lets define a simple array which will be the data we provide to d3 to create our labels. In this snippet we find the use of the
data method, which will allow us to connect data elements to the DOM, we’re going to create a selection and use .data() to bind our labels to the selection. The last instruction will help us create
g elements that will be in charge of containing our label elements. A reference to the selection was created with the variable
labels, this will let us append the desired elements for each element in our labels data array.
Scales, axis and labels were created in the previous snippets, we just need to render our bars with the help of all the elements mentioned before.
Lets create a
g element which will contain all our bars and give it the
data-container class. We will also be in need of data binding to connect our movie elements to the DOM and build a
g element for each of our movies contained in our dataset. We will need to correctly transform the
g elements so they translate to the correct position, this action will be managed by using the
xScale and give it as argument the movie name. The chart now has
g elements with the class
movie-container movie-container--name-of-the-movie for each movie. The snippet will add some
rects to the
g elements classed
movie-container, this is just to show how the
If we inspect our jsFiddle result we will see the following structure, we can move on and render the rating’s values.
Rating’s values must be rendered inside each of the movie’s containers. We will make use of our
movies variable which currently holds the selection of every
movie-container and use again data binding to connect the movie’s ratings values to the DOM and build a
g element for each of our movie’s ratings, this elements will have the class
rating-container. Inside of each of the
rating-container elements we will append a
rect which will represent the value of the current rating. The height of the
rect will be calculated by subtracting the output we get from inputing the rating value into our
yScale to the height. The width will be defined by the
ratingsScale bandwidth, finally we use the
ratingsScale to translate the
rect in the x position and the
yScale to translate it in the y position.
We finally have our chart, I will cook up the next tutorial in how to add interaction to our chart. Hope you find this useful.