Bar chart with negative values

OK, let’s say you have an array of numbers as your dataset, and this includes some positive and negative values:

var data = [-15, -20, -22, -18, 2, 6, -26, -18];

You’ll want two scales to construct a bar chart. You need one quantitative scale (typically a linear scale) to compute the bar positions along the x-axis, and a second ordinal scale to compute the bar positions along the y-axis.

For the quantitative scale, you typically need to compute the domain of your data, which is based on the minimum and maximum value. An easy way to do that is via d3.extent:

var x = d3.scale.linear()
    .domain(d3.extent(data))
    .range([0, width]);

You might also want to nice the scale to round the extent slightly. As another example, sometimes you want the zero-value to be centered in the middle of the canvas, in which case you’ll want to take the greater of the minimum and maximum value:

var x0 = Math.max(-d3.min(data), d3.max(data));

var x = d3.scale.linear()
    .domain([-x0, x0])
    .range([0, width])
    .nice();

Alternatively, you can hard-code whatever domain you want.

var x = d3.scale.linear()
    .domain([-30, 30])
    .range([0, width]);

For the y-axis, you’ll want to use rangeRoundBands to divide the vertical space into bands for each bar. This also lets you specify the amount of padding between bars. Often an ordinal scale is used with some identifying data—such as a name or a unique id. However, you can also use ordinal scales in conjunction with the data’s index:

var y = d3.scale.ordinal()
    .domain(d3.range(data.length))
    .rangeRoundBands([0, height], .2);

Now that you’ve got your two scales, you can create the rect elements to display the bars. The one tricky part is that in SVG, rects are positioned (the x and y attributes) based on their top-left corner. So we need to use the x– and y-scales to compute the position of the top-left corner, and that depends on whether the associated value is positive or negative: if the value is positive, then the data value determines the right edge of the bar, while if it’s negative, it determines the left edge of the bar. Hence the conditionals here:

svg.selectAll(".bar")
    .data(data)
  .enter().append("rect")
    .attr("class", "bar")
    .attr("x", function(d, i) { return x(Math.min(0, d)); })
    .attr("y", function(d, i) { return y(i); })
    .attr("width", function(d, i) { return Math.abs(x(d) - x(0)); })
    .attr("height", y.rangeBand());

Lastly, you can add an axis to display tick marks on top. You might also compute a fill style (or even a gradient) to alter the differentiate the appearance of positive and negative values. Putting it all together:

Leave a Comment