Richard Hunter
  • Projects
  • Writings
  • Contact

creating a bar chart using D3
created: Jul 20 2023
edited: Aug 09 2023

a tutorial demonstrating how to create a vertical bar chart using popular data visualisation tool D3

bar charts are a common form of data visualisation and are probably familiar to most people. for those starting in D3, a bar chart is likely one of the first thing you will want to try doing. this tutorial doesn't go into the weeds about how D3 works, so some of the concepts may be confusing to those who are not familiar with this library. however, all the code is explained, to the best of my abilities, so it is hoped that this will be a good starting point for those wishing to learn D3, or else just a handy place to copy and paste code from for those looking for quick and dirty solutions. at the end of this article will be a list of what I think are useful resources for those wishing to dig deeper into D3

create or retrieve data

the first thing to do is to create some data. for our purposes, we will just hardcode it. the only requirement is that the data consist of an array of items. the reason for this is that each item represents a different bar, or category, in the chart. the structure of the item is less important because we can write functions to access it. obviously, it is best when the structure of the item is simple and easy to understand

const data = [{
    category: 'football',
    value: 9
  },
  {
    category: 'tennis',
    value: 7
  },
  {
    category: 'athletics',
    value: 5
  },
  {
    category: 'gymnastics',
    value: 3
  },
  {
    category: 'chess',
    value: 2
  },
];

define main dimensions

it’s good to use variables to store values. it makes code more readable by giving names to values, and also allows these variables to be reused across the code and changed in one place. the SVG container will have a height, a width, and margins. the margins are necessary to make space for axes. in d3, it is convention to define the margins in an object.

const width = 500;
const height = 400;

const margin = {
  top: 30,
  right: 20,
  bottom: 40,
  left: 30,
};

create SVG container

this step will be repeated for all charts. we select the body element and append an svg element to it, configure it with a width and a height using the variables defined in the previous step, and we also give it a class name, allowing us to target it from within a stylesheet.

const width = 500;
const height = 400;

d3.select('body')
  .append('svg')
  .attr('width', width)
  .attr('height', height)
  .attr('class', 'chart-container')

scales

we need a scale for both the categories and the values. for the categories, we use scaleBand as we are handling discrete values. the domain is the array of categories from our data, e.g., [‘football’, ‘tennis’, ‘athletics’, ‘gymnastics’, ‘chess’]. the range is equivalent to the x coordinates of the two ends of the horizontal axis. in this case, the axis extends to the left and right margins. we add padding to give space between and around the bars

const categoriesScale = d3.scaleBand()
  .domain(data.map(d => d.category))
  .range([margin.left, width - margin.right])
  .paddingInner(0.5)
  .paddingOuter(0.2)

for the values scale, we use scaleLinear, since we are handling continuous values. the domain goes from 0 to the maximum value within the data. similar to before, the range is equivalent to the y coordinates of the two ends of the vertical axis - but with a twist: the start of the range is the y coordinate corresponding the bottom margin of the chart and the end of the range corresponds to the top margin. the reason we do this is because the SVG coordinate system has the 0 y coordinate at the top of the svg container, whilst the coordinate system in our chart has the 0 y coordinate at the bottom. note the use of variables to contain the min and max domain values

const minValue = 0;
const maxValue = d3.max(data.map(d => d.value));

const valuesScale = d3.scaleLinear()
  .domain([minValue, maxValue])
  .range([height - margin.bottom, margin.top])

draw bars

the scales defined in the previous step are used to calculate the dimensions of the bars. the following code will likely be confusing for those not already familiar with D3. the key concept to understand is that, in D3, data is bound to HTML or SVG elements within the DOM. this allows this data to be conveniently accessed in code for manipulating the elements that represent it. in our case, the bars in our chart are represented using rect SVG elements, so each rect element has the data for the corresponding category bound to it.

what this code does is:

  1. append a g element to the svg element
  2. for each item in the data array:
    • add a rect element to the group
    • bind the item to the rect element
    • set the x and y coordinates of the rect element
    • set the width and height of the rect element
    • set the fill color for the rect element
svg.append('g')
  .selectAll('rect')
  .data(data)
  .enter()
  .append('rect')
  .attr('x', d => categoriesScale(d.category))
  .attr('width', categoriesScale.bandwidth)
  .attr('height', d => height - margin.bottom - valuesScale(d.value))
  .attr('y', d => valuesScale(d.value))
  .attr('fill', d => colorScale(d => d.category))

for the x coordinate, the callback passed as the second argument to .attr() receives as an argument the item of data that it is working with. e.g. {category: ‘football’, value: 9}. this allows us to extract the category and use this to obtain the correct xCoordinate from the categoriesScale. we do something similar for the y coordinate using valuesScale. the width of each bar is the same and can be obtained from the bandwidth property of categoriesScale. the setting of the height and fill attributes require special attention

it might be expected that calling valuesScale(d.value) would produce the height in pixels of the bar. however, it’s not as simple as this because of how we previously configured the range of the scale. instead, the height of the bar is actually the height of the container minus the value returned by the scale, minus the bottom margin.

we use another type of scale to calculate colors for the bars: an ordinal scale. this is somewhat similar to a Map where the keys are the category names and the values are different colors (supplied by d3.schemeCategory10).

const colorScale = d3.scaleOrdinal()
  .domain(data.map(d => d.category))
  .range(d3.schemeCategory10)

Axes

the last thing we need to do is add in the x and y axes.

first, we create them using d3 functions, passing in the previously created scales:

const xAxis = d3.axisBottom(categoriesScale)
const yAxis = d3.axisLeft(valuesScale)

then we attach them to the DOM whilst applying translations to push them into place

svg.append('g')
  .attr('transform', `translate(0, ${height - margin.bottom})`)
  .call(xAxis);

svg.append('g')
  .attr('transform', `translate(${margin.left}, 0)`)
  .call(yAxis)

the x axis is positioned at the top of the container and has to be pushed down to the level of the bottom margin, whilst the y axis is positioned at the far left of the container and needs to be shifted rightwards to the position of the left margin

Featured writings

creating a bar chart using D3

a tutorial demonstrating how to create a vertical bar chart ...

making sense of Angular forms

a deep dive into Angular's two form modules...

Angular Resolution Modifiers and scope

how resolution modifiers like @Host and @Self affect injecto...

Injectors in Angular Dependency Injection

a deep dive into the scope of Component and Directive inject...

commentary on Kara Erickson's talk on Angular Forms

commentary on a talk given by Angular Core developer Kara Er...

Building my first computer game

My first attempt at creating a computer game in Javascript...

Animated page transitions in a Single Page App

In a Single Page App, we can take control of the routing pro...

Problems with Redux and why Streams are better

A discussion on some of the drawbacks of using Redux within ...

Implementing Angular's Hero app using React

Implementing Angular documentation's Hero app using React. ...

Dependency Injection

A discussion on Dependency Injection and a library I have wr...

Ways of making CSS clip-path property work better

Ways of making the clip path better...

Carracci: a UML diagram editing tool

Carracci is a project that I have been working on recently i...
  • Home
  • Projects
  • Writings
  • Contact