Line graph with error bars

Posted .

Creating a line graph with error bars is a bit more involved. There are no generator functions for error bars, so we just have to created them ourselves, though using the data and join methods this is going to be fairly easy.

We start with a line graph, don’t forget to make sure our y axis is large enough to include error bars. We are using the same data as our line graph but we took the average for each hour of the day over the entire year.

Here is the source of the final graph.

Joining Data

A rather common thing to do while drawing graphs is draw something for each data point you can do this by combining the selectAll, data and join functions. For example here we draw a path for each datapoint and move it to a point in the graph based on the data:

svg.selectAll("path")
  .data(data)
  .join("path")
  .attr("d", something)
  .attr("transform",
    d => `translate(${x(d.date)}, ${y(d.temperature)})`
  )
  .attr("stroke", "black")

The basic pattern goes as follows:

  1. Define what elements the data will be bound to (selectAll).
  2. Supply the data that will be bound (data).
  3. Define how the data will be bound to the elements (join).

The initial selectAll might be a bit confusing, after all why select paths if we’re going to add them? But this pattern is also used to update the graph as data is manipulated. And then it makes sense to explicitly include the elements that may already exist and may already have data bound to them.

The bit of code above is short for this, but since we were only adding things we didn’t need to bother with update or exit:

svg.selectAll("path")
  .data(data)
  .join(
    enter => enter.append("path")
      .attr("d", something)
      .attr("transform",
        d => `translate(${x(d.date)}, ${y(d.temperature)})`
      )
      .attr("stroke", "black"),
    update => update,
    exit => exit.remove()
  )

Symbols

One of the many convenience functions d3 has is the symbol function. The symbol function and its associated functions are used to generate strings that describe a path’s d attribute for a symbol. For example here we create the d for a circle:

let circle = d3.symbol()
  .type(d3.symbolCircle)
  .size(16)

Note that circle here is itself a function that returns d. We can then use it to add a circle to the center of our our graph like this:

svg.append("path")
  .attr("d", cirlce())
  .attr("transform",
    `translate(${width/2},${height/2})`
  )
  .attr("stroke", "black")

Error bars

So we can then use join to add error bars to our graph. We start by adding a group <g> to our graph that will contain all of our error bars. Then we use the join pattern to bind our data to a series of sub groups that will contain the elements that describe our error bar.

svg.append("g")
  .selectAll("g")
  .data(hours)
  .join(enter => {
    let g = enter.append("g")
    /* further error bar stuff goes here */
  })

So now we have to actually draw error bars, so we need to add a path that creates the classic I shape of an error bar. As far as we could tell there is no generator for error bars in d3. But there is a d3.path helper function that allows you to more easily create a paths d attribute using functions as moveTo and lineTo (and other like arcTo but we don’t need those here). We can use the d3.path function to create the error bar like so:

g.append("path")
  .attr("d", d => {
    let p = d3.path()
    // Vertical line
    p.moveTo(x(d.hour), y(d.avg - d.sde))
    p.lineTo(x(d.hour), y(d.avg + d.sde))
    // Bottom error bar
    p.moveTo(x(d.hour) - 4, y(d.avg - d.sde))
    p.lineTo(x(d.hour) + 4, y(d.avg - d.sde))
    // Top error bar
    p.moveTo(x(d.hour) - 4, y(d.avg + d.sde))
    p.lineTo(x(d.hour) + 4, y(d.avg + d.sde))
    return p.toString()
  })
  .attr("stroke", "seagreen")
  .attr("stroke-width", 1)

Then let’s add a small dot at the point where our errorbars intersect with our line using the symbol function we described earlier:

g.append("path")
  .attr("d", circle())
  .attr("transform",
    d => `translate(${x(d.hour)}, ${y(d.avg)})`
  )

Then by putting the last three bits of code together we can add error bars to our graph. The result can be seen in fig. 1.

DecDecDecDecDecDecDecDecJanHour4647484950515253545556575859Temperature (F°)