Search code examples
javascriptsvgd3.jsbar-chartrect

Always center bar on ordinal for bar charts


Let's say you have a d3.js scale as so (we're assuming horizontal bar chart here)

var yScale = d3.scale.ordinal()
            .rangeRoundBands([0,height]); //height of chart

A common way to then create bars is to append rects to your svg. Below is some common sample code. I've excluded various other attributes in order to just focus on y and height:

svg.selectAll(".bar")
  .data(data)
.enter().append("rect")
  .attr("y", function(d) { return yScale(d); })
  .attr("height", yScale.rangeBand());

However, for graphs of just 2-3 elements, it causes each band to be very large. I want to enable a 'maxBarHeight' stipulation, but I'm having trouble ensuring that the bar is always centered on the ordinal. This is what I have so far:

svg.selectAll(".bar")
  .data(data)
.enter().append("rect")
  .attr("y", function(d) {
    if(yScale.rangeBand() > maxBarHeight)
      return yScale(d) + (maxBarHeight / 2); //This doesn't work
    else
      return yScale(d);
  })
  .attr("height", (yScale.rangeBand() > maxBarHeight ? maxBarHeight : yScale.rangeBand()))

Is there a simpler way to control bar size while maintaining alignment?


Solution

  • I've found the best way to control bar size and centering is to not mess around with the x/y or width/height attributes of the rect, but to instead use the padding parameters in rangeBands() or rangeRoundBands(). Below are some example charts plotting gender data:

    Horizontal Chart (2 bars), default ordinal

    var yScale = d3.scale.ordinal().rangeRoundBands([0,chartHeight]); default_ordinal

    Horizontal Chart (2 bars), ordinal with inner padding

    var yScale = d3.scale.ordinal().rangeRoundBands([0,chartHeight],.7); inner_padding

    Horizontal Chart (2 bars), ordinal with inner & outer padding

    var yScale = d3.scale.ordinal().rangeRoundBands([0,chartHeight],.5,.1); enter image description here

    This method will let you adjust the bar size while maintaining alignment with the ordinal.