github twitter linkedin email
Create a responsive Bar Chart with d3.js
May 3, 2021
4 minutes read

For a while now, I’ve been wanting to dust off my skills in creating responsive graphics with d3.js. In the past I’ve used it to create some visualizations of complex networks or simple interactive dashboards for my former clients.

I went back to freecodecamp’s course on data visualization which provides a highly recommended introduction to d3 for all those with some experience in web development, i.e., html, css and javascript. Below is my implementation of their first out of five Coding Challenges that visualizes a time series of quarterly US GDP with a bar chart. I’m aware that there are already tons of examples for such kinds of graphics in the web, but since every person has some unique coding style (I usually have a hard time to fully comprehend all details of other peoples' example code) I keep it here for my personal reference with the hope that it is useful for some other people, too.

In particular, this chart is meant to respond to mouseover events by displaying a tooltip with more details about the data in the respective bar. I tried not to overload the chart with transitions in order to keep the example focused and concise:

This example provides a very basic template for the following d3-operations:

  1. Load the data from an external source. For this purpose everything you do with the data needs to be enclosed in the d3.json() function.
  2. Append an svg object for the bar chart with specified width and height to the body or a div in your webpage
  3. Use appropriate scales to convert the domain of the data to the range of the svg. We use d3.scaleTime() for transforming the input dates that are given as a string of the format YYYY-MM-DD. Similarly, we use d3.scaleLinear() for transforming the corresponding GDP values.
  4. Draw and transform/translate horizontal and vertical axes to their correct positions in the svg. We use d3.axisBottom() and d3.axisLeft() for the horizontal and vertical axis, respectively.
  5. Draw the individual bars of the chart and define corresponding mouseover events that trigger the visibility of a tooltip. For this purpose we initialize a single div for the tooltip and change its visibility and position according to the position of the mouse.
  6. Finalize the chart by adding appropriate labels and a title.

The entire implementation is given below or in this codepen app The inline-comments correspond to the operations outlined above. There are some additional css-styles that are mostly optional. The only important style is that for .bar:hover which changes the color of the bar that the mouse currently hovers over. You can copy the implementation into a single file and run it locally on your own machine.

<!DOCTYPE html>
<meta charset="utf-8">
<html>

<head>
  <script src="https://d3js.org/d3.v4.min.js" charset="utf-8"></script>
</head>

<style>
* {
    font-family: sans-serif;
}

.bar{
    fill: #16a085;
}

.bar:hover{
    fill: white;
}

#tooltip {
    background-color: #ecf0f1;
    visibility: hidden;
    position: absolute;
    opacity: 0.8;
    padding: 10px;
    vertical-align: middle;
    border-radius: 5px;
    text-align: center;
    width: 180px;
}

#title {
    text-anchor: middle;
    font-size: 22px;
}

.label {
    text-anchor: middle;
} </style>

<body>

<div id=container align="center"></div>

<script type="text/javascript">

var url = "https://raw.githubusercontent.com/freeCodeCamp/ProjectReferenceData/master/GDP-data.json"

// 1. Load the data from external source
d3.json(url, function(data) {

    // 2. Append svg-object for the bar chart to a div in your webpage
    // (here we use a div with id=container)
    var width = 700;
    var height = 500;
    var margin = {left: 90, top: 70, bottom: 50, right: 20};

    const svg = d3.select("#container")
                  .append("svg")
                  .attr("id", "svg")
                  .attr("width", width)
                  .attr("height", height)

    // 3. Define scales to translate domains of the data to the range of the svg
    var xMin = d3.min(data["data"], (d) => d[0]);
    var xMax = d3.max(data["data"], (d) => d[0]);

    var yMin = d3.min(data["data"], (d) => d[1]);
    var yMax = d3.max(data["data"], (d) => d[1]);

    var xScale = d3.scaleTime()
                   .domain([new Date(xMin), new Date(xMax)])
                   .range([margin.left, width-margin.right])

    var yScale = d3.scaleLinear()
                   .domain([0, yMax])
                   .range([height-margin.bottom, margin.top])

    // 4. Draw and transform/translate horizontal and vertical axes
    var xAxis = d3.axisBottom().scale(xScale)
    var yAxis = d3.axisLeft().scale(yScale)

    svg.append("g")
       .attr("transform", "translate(0, "+ (height - margin.bottom) + ")")
       .attr("id", "x-axis")
       .call(xAxis)

    svg.append("g")
       .attr("transform", "translate("+ (margin.left)+", 0)")
       .attr("id", "y-axis")
       .call(yAxis)

    // 5. Draw individual bars and define mouse events for the tooltip
    var barwidth = (xScale.range()[1] - xScale.range()[0]) / data["data"].length

    const tooltip = d3.select("body")
                      .append("div")
                      .attr("id", "tooltip")
                      .style("visibility", "hidden")

    svg.selectAll("rect")
       .data(data["data"])
       .enter()
       .append("rect")
       .attr("x", (d) => xScale(new Date(d[0])))
       .attr("y", (d) => yScale(d[1]))
       .attr("width", barwidth)
       .attr("height", (d) => height - margin.bottom - yScale(d[1]))
       .attr("class", "bar")
       .attr("data-date", (d) => d[0])
       .attr("data-gdp", (d) => d[1])
       .on("mouseover", function(d){
           tooltip.style("visibility", "visible")
                  .style("left", event.pageX+10+"px")
                  .style("top", event.pageY-80+"px")
                  .attr("data-date", d[0])
                  .html(d[0] + "</br>" + d[1] + " Billion USD" )
       })
       .on("mousemove", function(){
           tooltip.style("left", event.pageX+10+"px")
       })
       .on("mouseout", function(){
           tooltip.style("visibility", "hidden")
       })

     // 6. Finalize chart by adding title and axes labels
     svg.append("text")
        .attr("x", margin.left + (width - margin.left - margin.right) / 2)
        .attr("y", height - margin.bottom / 5)
        .attr("class", "label")
        .text("Date");

      svg.append("text")
         .attr("y", margin.left/4)
         .attr("x", -height/2)
         .attr("transform", "rotate(-90)")
         .attr("class", "label")
         .text("GDP [Billion USD]");

    svg.append("text")
        .attr("x", margin.left + (width - margin.left - margin.right) / 2)
        .attr("y", margin.top / 2)
        .attr("id", "title")
        .text("United States GDP");
}) 
</script>
</body>
</html>

Back to posts