JavaScript
Article

Creating Simple Line and Bar Charts Using D3.js

By Jay Raj

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.

  • Jeeva Mohan

    This example is really good. I have implemented the Line chart, I am able to view the single line chart with X axis and y axis. I want to display multiple lines in the line chart. Is it possible? if so can you share any link or any exercise with multiple line chart.

  • 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..

  • robert

    I think there is a little mistake in the definition of the y-range (or just check the position of y-axis in the demo, it wrong imo). The minimum value should be mapped to (height-margin.bottom), while maximum should be at margin.top. See the fixed version below:

    d3.scale.linear().range([HEIGHT – MARGINS.bottom, MARGINS.top])

    Thanks for the example,
    rb

  • Benjamin Schmidt

    thank you very much

  • john

    Thanks heaps!

  • aaron

    the line chart example demo is not working on codepen for me either, tried on Chrome / Mavericks

  • Jose Manuel Jimenez

    Perhaps the definition of the yRange is wrong. You say:
    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; })
    ])
    That is, the minimun value on the domain should be positioned on the bottom of the svg area. To achieve this, this position should be: HEIGHT – MARGINS.bottom (the full height of the area minus the space for the bottom margin). This mistake only comes out if bottom and top have different values

  • Maria Melton

    Hello, Great tutorial! If I have an array of JSON line objects (each line has: x1, y1, x2, y2, and a name label), how do I draw all of them? It’s not a continuously connected line, it’s a bunch of different lines. I’m just having trouble coming up with a helper function to do this. Thank you!

  • http://vykt.blogspot.in devang nathwani

    Thanks Jay

  • Brahma

    Thanks Jay… Its much helpful for me as a beginner to D3 charts

  • Brahma

    Thanks Jay… Its much helpful for me as a beginner to D3 charts

  • http://www.i-visionblog.com/ s.shivasurya

    really useful!

Recommended

Learn Coding Online
Learn Web Development

Start learning web development and design for free with SitePoint Premium!

Get the latest in JavaScript, once a week, for free.