5

First I would like to find out if anyone knows of a D3 example showing grouped bar charts with error bars? The closest things I have found are:

http://tenxer.github.io/xcharts/examples/ (Example Custom Vis Type: Error Bars) http://bl.ocks.org/chrisbrich/5044999 (error bars for points)

I have a working example of a grouped bar chart and I would like to add an error bar to each bar in the graph showing the MOE. What steps would I need to take to accomplish this? How would I calculate the position of the line? This is what I think needs to be done, please help me fill in the steps I need to take.

  1. Create a SVG line

  2. Calculate the ymin and ymax of the line by taking d3.max of d.value + the MOE and d3.max of d.value - MOE

  3. Append

This doesn't work but is it on the right track?

var line = d3.svg.line()
    .x(function(d) {return x(d.state); })
    .y0(function(d) {return y(d.value - d.moe); })
    .y1(function(d) {return y(d.value + d.moe); })
    .interpolate("linear");

var errorBarSVG = d3.select("body").append("svg")

var errorBar = errorBarSVG.append("path")
    .attr("d", line(data))
    .attr("stroke", "red")
    .attr("stroke-width", 1.5);
bailey
  • 353
  • 6
  • 19

1 Answers1

5

If you just want straight-line error bars (without top and bottom horizontal lines), yes, you could use an area or line generator (only area generators have y0/y1 methods).

However, you wouldn't call the path generator on all the data at once, you would have to create a selection of paths joined to the data, and then pass the data object as a one (area generator) or two-point (line generator) array to the generator function.

This should work:

var errorBarArea = d3.svg.area()
    .x(function(d) {return x(d.state); })
    .y0(function(d) {return y(d.value - d.moe); })
    .y1(function(d) {return y(d.value + d.moe); })
    .interpolate("linear");

var errorBarSVG = d3.select("body").append("svg")

var errorBars = errorBarSVG.selectAll("path")
         .data(data);

errorBars.enter().append("path");

errorBars.attr("d", function(d){return errorBarArea([d]);}) 
             //turn the data into a one-element array 
             //and pass it to the area function
    .attr("stroke", "red")
    .attr("stroke-width", 1.5);

This will create a zero-width area path for each bar, connecting the +/- points in a straight line.

If you used a line generator function, you would have to calculate the top and bottom Y-values when you turned the data into an array:

errorBars.attr("d", function(d){
                  return errorBarLine([{x:d.x, y:d.value - d.moe},
                                       {x:d.x, y:d.value + d.moe}]);
               })

Alternately, you could use d3.svg.diagonal to create straight-line error bars. A diagonal function is specifically designed to take a single data object and extract start and end line points from it; you would then have to customize the source and target accessor functions to create objects like the ones in the code sample above.

However, if you want a custom-shaped error bar with horizontal top and bottom lines, you'll need to write your own function to create the path "d" attribute based on a data object. I discuss doing that in this answer, for an error bar the shape would actually be much simpler, since it's all just straight lines. You might find this tutorial on the path directions syntax a useful reference.

Finally, you might also want to re-write your bar chart code so that each bar is represented by a <g> element joined to the data, and then you append both the rectangle and the error bar path within it without calculating separate data joins.

Community
  • 1
  • 1
AmeliaBR
  • 27,344
  • 6
  • 86
  • 119
  • Thanks your answers are always very helpful! I'm getting an error "Error: Problem parsing d="MNaN,NaNLNaN,NaNZ" at this line: errorBars.attr("d", function(d){return errorBarArea([d]);}) – bailey Mar 25 '14 at 16:44
  • It gets a little complicated because of the grouped data structure. The data that is joined to the error bar paths has to be the data for each individual bar (so you can axis the value), but in the same data object it also has to have the margin of error. I've updated to (a)copy the margin of error (which you defined per group) into the individual data points in the mapping function; (b)change the scales in the area generator to match the data; (c)make the error bar a *nested* selection within each state group; and (d)make your sample moe big enough to be seen! http://jsfiddle.net/CgqrY/5/ – AmeliaBR Mar 25 '14 at 21:00
  • Awesome. So if there was a moe for each year category "under 5 years moe" or "5 to 14 years moe" rather than an moe per state group the step that copies the moe into the individual data points could be skipped? could moe for each category all still be called "moe" or would they need to all have their own unique name? – bailey Mar 25 '14 at 22:03
  • Yes, if you had m.o.e values per datapoint then you wouldn't need to duplicate. If *some* of your datapoints had there own values, others were per category, you'd need a check of the form `moe = category.moe || state.moe` to assign the first if it exists and if not, assign the second (exact code would of course depend on your data structure). Either way, you would want the final data object to have the *same* property name, so that the graphing functions can access it without complicated checks. – AmeliaBR Mar 26 '14 at 15:54