I need to create a d3 bar chart that can have negative values. Ideally the axis zero position should be calculated based on the extent of the data, but I'd settle for a solution that assumes symmetric positive and negative extent, i.e. that it would be always in the middle of the chart.
Here's an example of what I'd like to achieve.
If you want to show how multiple units does comparison to each other based on the same criteria clearly, you can use the positive negative bar chart which can display positive and negative development very good as below screenshot shown.
Negative numbers are to the left of the origin on the x-axis and below the origin on the y-axis. To graph with negative numbers, start at the origin and go to the left if the x-value is negative, and downward if the y-coordinate is negative. Points in quadrant I will always follow the format (+, +).
Therefore, use the column chart when you have negative values in your data set. In conclusion, while the column chart is helpful to facilitate all comparison-based analysis, it is better to use the bar chart when your data labels are long, or you have too many data sets to display.
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:
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With