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:
- Define what elements the data will be bound to (
selectAll
). - Supply the data that will be bound (
data
). - 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.