Creating Simple Line and Bar Charts Using D3.js

Jay Raj
Jay Raj
Share

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.

Frequently Asked Questions (FAQs) about Creating Bar Charts Using D3.js

How can I add labels to the bars in my D3.js bar chart?

Adding labels to the bars in your D3.js bar chart can enhance the readability and understanding of your chart. To do this, you can append text elements to each bar in your chart. Here’s a simple example:

svg.selectAll("text")
.data(data)
.enter()
.append("text")
.text(function(d) {
return d.value;
})
.attr("x", function(d) {
return xScale(d.name) + xScale.bandwidth() / 2;
})
.attr("y", function(d) {
return yScale(d.value) - 10;
});
In this code, we’re appending a text element for each data point, setting the text as the data value, and positioning it in the middle of the corresponding bar and slightly above the top of the bar.

How can I create a horizontal bar chart using D3.js?

Creating a horizontal bar chart in D3.js involves swapping the roles of the x and y axes. Instead of mapping your data values to the height of the bars, you’ll map them to the width. Here’s a basic example:

var xScale = d3.scaleLinear()
.domain([0, d3.max(data)])
.range([0, chartWidth]);

var yScale = d3.scaleBand()
.domain(d3.range(data.length))
.rangeRound([0, chartHeight])
.padding(0.1);

svg.selectAll("rect")
.data(data)
.enter()
.append("rect")
.attr("y", function(d, i) {
return yScale(i);
})
.attr("width", function(d) {
return xScale(d);
})
.attr("height", yScale.bandwidth());
In this code, we’re using a linear scale for the x-axis (representing the data values) and a band scale for the y-axis (representing the data categories). Each bar’s y position is determined by its index in the data array, its width by its data value, and its height by the bandwidth of the y scale.

How can I add a tooltip to my D3.js bar chart?

Tooltips can provide additional information about the data points in your chart when the user hovers over them. To add a tooltip, you can append a div element to the body of your HTML document and update its content and position in response to mouse events. Here’s a basic example:

var tooltip = d3.select("body").append("div")
.attr("class", "tooltip")
.style("opacity", 0);

svg.selectAll("rect")
.data(data)
.enter()
.append("rect")
.on("mouseover", function(d) {
tooltip.transition()
.duration(200)
.style("opacity", .9);
tooltip.html("Value: " + d)
.style("left", (d3.event.pageX) + "px")
.style("top", (d3.event.pageY - 28) + "px");
})
.on("mouseout", function(d) {
tooltip.transition()
.duration(500)
.style("opacity", 0);
});
In this code, we’re creating a tooltip div with an initial opacity of 0. On mouseover, we transition the tooltip to full opacity and set its content to the data value and its position to the mouse position. On mouseout, we transition the tooltip back to zero opacity.

How can I make my D3.js bar chart responsive?

Making your D3.js bar chart responsive involves updating the chart dimensions and redraw the chart whenever the window size changes. Here’s a basic example:

function resize() {
var width = parseInt(d3.select("#chart").style("width"));
var height = parseInt(d3.select("#chart").style("height"));

xScale.range([0, width]);
yScale.range([height, 0]);

svg.attr("width", width)
.attr("height", height);

svg.selectAll("rect")
.attr("width", xScale.bandwidth())
.attr("x", function(d) { return xScale(d.name); })
.attr("y", function(d) { return yScale(d.value); })
.attr("height", function(d) { return height - yScale(d.value); });
}

d3.select(window).on('resize', resize);
resize();
In this code, we’re defining a resize function that updates the range of the scales, the size of the SVG container, and the attributes of the bars based on the current size of the chart container. We call this function initially to draw the chart and whenever the window size changes to update the chart.

How can I animate the bars in my D3.js bar chart?

Animating the bars in your D3.js bar chart can make your chart more engaging and help to highlight changes in the data. To animate the bars, you can use D3’s transition method. Here’s a basic example:

svg.selectAll("rect")
.data(data)
.enter()
.append("rect")
.attr("y", function(d) {
return yScale(d.value);
})
.attr("height", 0)
.transition()
.duration(1000)
.attr("height", function(d) {
return chartHeight - yScale(d.value);
});
In this code, we’re initially setting the height of the bars to 0 and their y position to the corresponding data value on the y scale. Then, we’re transitioning the height to the actual height based on the data value and the y position to the bottom of the chart over a duration of 1000 milliseconds.

How can I sort the bars in my D3.js bar chart?

Sorting the bars in your D3.js bar chart can help to highlight the ranking of your data points. To sort the bars, you can sort your data array and then update the domain of your scale and the attributes of your bars. Here’s a basic example:

data.sort(function(a, b) {
return d3.descending(a.value, b.value);
});

xScale.domain(data.map(function(d) {
return d.name;
}));

svg.selectAll("rect")
.data(data)
.transition()
.duration(1000)
.attr("x", function(d) {
return xScale(d.name);
});
In this code, we’re sorting the data array in descending order of the data values. Then, we’re updating the domain of the x scale to reflect the new order of the data points. Finally, we’re transitioning the x position of the bars to their new positions based on the updated scale over a duration of 1000 milliseconds.

How can I add a title and axis labels to my D3.js bar chart?

Adding a title and axis labels to your D3.js bar chart can improve its readability and understanding. To add a title and axis labels, you can append text elements to your SVG container. Here’s a basic example:

svg.append("text")
.attr("class", "title")
.attr("x", chartWidth / 2)
.attr("y", 0 - margin.top / 2)
.attr("text-anchor", "middle")
.text("My Bar Chart");

svg.append("text")
.attr("class", "axis-label")
.attr("x", chartWidth / 2)
.attr("y", chartHeight + margin.bottom)
.attr("text-anchor", "middle")
.text("X Axis Label");

svg.append("text")
.attr("class", "axis-label")
.attr("transform", "rotate(-90)")
.attr("y", 0 - margin.left)
.attr("x", 0 - (chartHeight / 2))
.attr("dy", "1em")
.attr("text-anchor", "middle")
.text("Y Axis Label");
In this code, we’re appending three text elements to the SVG container: one for the title, positioned in the middle of the top margin; one for the x-axis label, positioned in the middle of the bottom margin; and one for the y-axis label, positioned in the middle of the left margin and rotated 90 degrees counter-clockwise.

How can I change the color of the bars in my D3.js bar chart?

Changing the color of the bars in your D3.js bar chart can help to differentiate your data points or highlight certain bars. To change the color of the bars, you can set the fill attribute of the bars. Here’s a basic example:

svg.selectAll("rect")
.data(data)
.enter()
.append("rect")
.attr("fill", "steelblue");
In this code, we’re setting the fill attribute of the bars to “steelblue”. You can replace “steelblue” with any valid color name, hex color code, or RGB color value.

How can I add a legend to my D3.js bar chart?

Adding a legend to your D3.js bar chart can help to identify the data categories represented by different colors or patterns in your bars. To add a legend, you can append a group element for each category and append a rectangle and a text element for the color and the label of each category. Here’s a basic example:

var legend = svg.selectAll(".legend")
.data(color.domain())
.enter().append("g")
.attr("class", "legend")
.attr("transform", function(d, i) { return "translate(0," + i * 20 + ")"; });

legend.append("rect")
.attr("x", chartWidth - 18)
.attr("width", 18)
.attr("height", 18)
.style("fill", color);

legend.append("text")
.attr("x", chartWidth - 24)
.attr("y", 9)
.attr("dy", ".35em")
.style("text-anchor", "end")
.text(function(d) { return d; });
In this code, we’re appending a group element for each category in the color scale’s domain, positioning it based on its index in the domain. Then, we’re appending a rectangle and a text element to each group, setting the fill of the rectangle and the text of the text element to the category.

How can I handle missing or null values in my D3.js bar chart?

Handling missing or null values in your D3.js bar chart can prevent errors and misleading representations of your data. To handle missing or null values, you can filter your data array before mapping it to your bars. Here’s a basic example:

data = data.filter(function(d) {
return d.value != null;
});

svg.selectAll("rect")
.data(data)
.enter()
.append("rect");
In this code, we’re filtering the data array to remove any elements with a null value before mapping it to the bars. This will prevent bars with undefined height from being drawn.