Creating Simple Line and Bar Charts Using D3.js

In a previous article, we learned how to implement bubble charts using D3.js, a JavaScript library for creating data-driven documents. D3.js helps to visualize data using HTML, SVG, and CSS. In this article, we’ll see how to implement line and bar charts using D3.js. Before moving on, you should download D3.js and be familiar with the material in my previous article.

Creating Line Charts

First, we’ll need some data to plot. We’re going to use the following data.

var lineData = [{
  x: 1,
  y: 5
}, {
  x: 20,
  y: 20
}, {
  x: 40,
  y: 10
}, {
  x: 60,
  y: 40
}, {
  x: 80,
  y: 5
}, {
  x: 100,
  y: 60
}];

We’re also going to need a <svg> element to plot our graph on.

<svg id="visualisation" width="1000" height="500"></svg>

Next, we need to create our x and y axes, and for that we’ll need to declare a domain and range. The domain defines the minimum and maximum values displayed on the graph, while the range is the amount of the SVG we’ll be covering. Both of the axes need to scale as per the data in lineData, meaning that we must set the domain and range accordingly. The code for drawing the axes is shown below.

var vis = d3.select('#visualisation'),
    WIDTH = 1000,
    HEIGHT = 500,
    MARGINS = {
      top: 20,
      right: 20,
      bottom: 20,
      left: 50
    },
    xRange = d3.scale.linear().range([MARGINS.left, WIDTH - MARGINS.right]).domain([d3.min(lineData, function(d) {
      return d.x;
    }), d3.max(lineData, function(d) {
      return d.x;
    })]),
    yRange = d3.scale.linear().range([HEIGHT - MARGINS.top, MARGINS.bottom]).domain([d3.min(lineData, function(d) {
      return d.y;
    }), d3.max(lineData, function(d) {
      return d.y;
    })]),
    xAxis = d3.svg.axis()
      .scale(xRange)
      .tickSize(5)
      .tickSubdivide(true),
    yAxis = d3.svg.axis()
      .scale(yRange)
      .tickSize(5)
      .orient('left')
      .tickSubdivide(true);

vis.append('svg:g')
  .attr('class', 'x axis')
  .attr('transform', 'translate(0,' + (HEIGHT - MARGINS.bottom) + ')')
  .call(xAxis);

vis.append('svg:g')
  .attr('class', 'y axis')
  .attr('transform', 'translate(' + (MARGINS.left) + ',0)')
  .call(yAxis);

In this code, we have defined the WIDTH, HEIGHT, and MARGINS for our graph. The xRange and yRange variables represent the domains for the respective axes. We set the range for our axes as per the left and right margins.

Next, since the domain is the data we will show on the graph, we need to get the min and max values from lineData. This is done using the d3.max() and d3.min() methods.

Next, we created our axes as per the xRange and yRange variables. For both axes, we have defined the scale as xRange and yRange for the X and Y axes, respectively. And then we simply appended both the axis to the SVG and applied the transform. Now, if we have a look at the Y axis it needs to be oriented to the left. Hence, we applied a left orientation to the yAxis. We have transformed both the axes, keeping the defined margins in view so that the axes don’t touch the SVG margins.

Here is a demo of the above code showing both axes.

Next, we need to apply the xRange and the yRange to the coordinates to transform them into the plotting space and to draw a line across the plotting space. We’ll be using d3.svg.line() to draw our line graph. For this, we need to create a line generator function which returns the x and y coordinates from our data to plot the line. This is how we define the line generator function:

var lineFunc = d3.svg.line()
  .x(function(d) {
    return xRange(d.x);
  })
  .y(function(d) {
    return yRange(d.y);
  })
  .interpolate('linear');

The interpolate('linear') call tells D3 to draw straight lines.

Next, we need to set the d attribute of the SVG path to the coordinates returned from the line function. This is accomplished using the following code.

vis.append('svg:path')
  .attr('d', lineFunc(lineData))
  .attr('stroke', 'blue')
  .attr('stroke-width', 2)
  .attr('fill', 'none');

We have set the line color using stroke. The line’s width is defined using stroke-width. We have set fill to none, as not to fill the graph boundaries. Here is a demo of the line graph with linear interpolation in action, and here is the same graph demo with basis interpolation.

Creating Bar Charts

Next, we’ll look at creating bar charts. Since, we already created our axes, we won’t need to reinvent the wheel. However, we will modifiy the existing code a bit. First, the sample data and code for creating our chart’s axes:

function InitChart() {

  var barData = [{
    'x': 1,
    'y': 5
  }, {
    'x': 20,
    'y': 20
  }, {
    'x': 40,
    'y': 10
  }, {
    'x': 60,
    'y': 40
  }, {
    'x': 80,
    'y': 5
  }, {
    'x': 100,
    'y': 60
  }];

  var vis = d3.select('#visualisation'),
    WIDTH = 1000,
    HEIGHT = 500,
    MARGINS = {
      top: 20,
      right: 20,
      bottom: 20,
      left: 50
    },
    xRange = d3.scale.linear().range([MARGINS.left, WIDTH - MARGINS.right]).domain([d3.min(barData, function(d) {
        return d.x;
      }),
      d3.max(barData, function (d) {
        return d.x;
      })
    ]),

    yRange = d3.scale.linear().range([HEIGHT - MARGINS.top, MARGINS.bottom]).domain([d3.min(barData, function(d) {
        return d.y;
      }),
      d3.max(barData, function (d) {
        return d.y;
      })
    ]),

    xAxis = d3.svg.axis()
      .scale(xRange)
      .tickSize(5)
      .tickSubdivide(true),

    yAxis = d3.svg.axis()
      .scale(yRange)
      .tickSize(5)
      .orient("left")
      .tickSubdivide(true);

  vis.append('svg:g')
    .attr('class', 'x axis')
    .attr('transform', 'translate(0,' + (HEIGHT - MARGINS.bottom) + ')')
    .call(xAxis);

  vis.append('svg:g')
    .attr('class', 'y axis')
    .attr('transform', 'translate(' + (MARGINS.left) + ',0)')
    .call(yAxis);
}

InitChart();

Here is a demo of the previous code. If you have a look at the Y axis, the scale starts at five. This minimum comes from our sample data, where 5 is the min Y value. Therefore, we need to scale the Y axis from 0. For that, we need to modify the domain of the yRange in the InitChart() function as shown below:

yRange = d3.scale.linear().range([HEIGHT - MARGINS.top, MARGINS.bottom]).domain([0,
  d3.max(barData, function(d) {
    return d.y;
  })]);

In the case of bar charts, we’ll be using ordinal scales instead of the linear scales. Ordinal scales help to maintain a discrete domain. For a more detailed info refer to the official documentation on ordinal scales.

We’ll also be using rangeRoundBands to divide the width across the chart bars. We’ll modify the xRange using ordinal scale and rangeRoundBands as shown below. Notice that we have also set the spacing between the bars to 0.1.

xRange = d3.scale.ordinal().rangeRoundBands([MARGINS.left, WIDTH - MARGINS.right], 0.1).domain(barData.map(function(d) {
  return d.x;
}));

Next, we need to create rectangular bars for the chart data. We’ll be binding our sample data to the rectangles, using the x and y coordinates to set the height and width of the rectangular bars. Here is how the code looks:

vis.selectAll('rect')
  .data(barData)
  .enter()
  .append('rect')
  .attr('x', function(d) { // sets the x position of the bar
    return xRange(d.x);
  })
  .attr('y', function(d) { // sets the y position of the bar
    return yRange(d.y);
  })
  .attr('width', xRange.rangeBand()) // sets the width of bar
  .attr('height', function(d) {      // sets the height of bar
    return ((HEIGHT - MARGINS.bottom) - yRange(d.y));
  })
  .attr('fill', 'grey');   // fills the bar with grey color

Here is a demo of our bar chart in action.

Adding Events

In order to improve interactivity, we can also attach events to the bars. We can attach an event to highlight the bar on mouseover. Here is how it can be accomplished:

vis.selectAll('rect')
  .data(barData)
  .enter()
  .append('rect')
  .attr('x', function(d) {
    return xRange(d.x);
  })
  .attr('y', function(d) {
    return yRange(d.y);
  })
  .attr('width', xRange.rangeBand())
  .attr('height', function(d) {
    return ((HEIGHT - MARGINS.bottom) - yRange(d.y));
  })
  .attr('fill', 'grey')
  .on('mouseover', function(d) {
    d3.select(this)
      .attr('fill', 'blue');
  });

In this code, the on('mouseover') adds an event handler that is invoked on mouse over, which makes the hovered bars blue. Here is a demo that illustrates this effect.

You might notice that the bars don’t turn grey again on mouseout. Let’s attach another event to revert it back to its previous color on mouse out. The updated code is shown below:

vis.selectAll('rect')
  .data(barData)
  .enter()
  .append('rect')
  .attr('x', function(d) {
    return xRange(d.x);
  })
  .attr('y', function(d) {
    return yRange(d.y);
  })
  .attr('width', xRange.rangeBand())
  .attr('height', function(d) {
    return ((HEIGHT - MARGINS.bottom) - yRange(d.y));
  })
  .attr('fill', 'grey')
  .on('mouseover', function(d) {
    d3.select(this)
      .attr('fill', 'blue');
  })
  .on('mouseout', function(d) {
    d3.select(this)
      .attr('fill', 'grey');
  });

And, here is a demo of the above code in action.

Conclusion

D3.js is an awesome JavaScript libray for data visualization. In this tutorial, we focused on creating fairly simple bar and line charts. If you’re interested in experimenting more, try adding additional visualization techniques from the D3 library to the charts in this article.

Win an Annual Membership to Learnable,

SitePoint's Learning Platform

  • Chris Gat

    Hmm, example provided don’t seem to work on codepen anymore

    • http://www.techillumination.in Jay

      @chrisgat:disqus it’s working perfectly fine. I tried in Google chrome.Which browser are you using ?

  • Avinash Wadhawan

    This is a good example… Thanks..