Create a gist now

Instantly share code, notes, and snippets.

@mbostock /.block
Last active Nov 30, 2017

Embed
What would you like to do?
Bar Chart with Negative Values
license: gpl-3.0

Say your dataset is an array of numbers, and includes both positive and negative values. Use two scales to construct the bar chart: a quantitative scale (such as a linear scale) to compute the bar positions along the x-axis, and an ordinal scale with rangeBands to compute the bar positions along the y-axis.

For the quantitative scale, compute the data domain (the minimum and maximum value) using d3.extent:

var x = d3.scale.linear()
    .domain(d3.extent(data, function(d) { return d.value; }))
    .range([0, width]);

Nicing the scale will extend the extent slightly to the nearest round numbers. If you want the zero-value to be centered in the middle of the canvas, take the greater of the minimum and maximum value by magnitude, or simply hard-code the desired domain.

For the y-axis, use rangeRoundBands to divide the vertical space into bands for each bar and specify the amount of padding between bars. The input (domain) to the ordinal scale is some identifying data—such as a name or a unique id. A simple such identifier is the data’s index:

var y = d3.scale.ordinal()
    .domain(data.map(function(d) { return d.name; }))
    .rangeRoundBands([0, height], .2);

Use both scales to position the bars. This is made slightly tricky in that SVG rects are positioned (the x and y attributes) by their top-left corner and cannot have a negative width or height. So, we must use the x- and y-scales to compute the position of the top-left corner, depending 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:

svg.selectAll(".bar")
    .data(data)
  .enter().append("rect")
    .attr("class", "bar")
    .attr("x", function(d) { return x(Math.min(0, d.value)); })
    .attr("y", function(d) { return y(d.name); })
    .attr("width", function(d) { return Math.abs(x(d.value) - 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.

name value
A -15
B -20
C -22
D -18
E 2
F 6
G 26
H 18
<!DOCTYPE html>
<meta charset="utf-8">
<style>
.bar--positive {
fill: steelblue;
}
.bar--negative {
fill: darkorange;
}
.axis text {
font: 10px sans-serif;
}
.axis path,
.axis line {
fill: none;
stroke: #000;
shape-rendering: crispEdges;
}
</style>
<body>
<script src="//d3js.org/d3.v3.min.js"></script>
<script>
var margin = {top: 20, right: 30, bottom: 40, left: 30},
width = 960 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
var x = d3.scale.linear()
.range([0, width]);
var y = d3.scale.ordinal()
.rangeRoundBands([0, height], 0.1);
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom");
var yAxis = d3.svg.axis()
.scale(y)
.orient("left")
.tickSize(0)
.tickPadding(6);
var svg = d3.select("body").append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
d3.tsv("data.tsv", type, function(error, data) {
x.domain(d3.extent(data, function(d) { return d.value; })).nice();
y.domain(data.map(function(d) { return d.name; }));
svg.selectAll(".bar")
.data(data)
.enter().append("rect")
.attr("class", function(d) { return "bar bar--" + (d.value < 0 ? "negative" : "positive"); })
.attr("x", function(d) { return x(Math.min(0, d.value)); })
.attr("y", function(d) { return y(d.name); })
.attr("width", function(d) { return Math.abs(x(d.value) - x(0)); })
.attr("height", y.rangeBand());
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
svg.append("g")
.attr("class", "y axis")
.attr("transform", "translate(" + x(0) + ",0)")
.call(yAxis);
});
function type(d) {
d.value = +d.value;
return d;
}
</script>
@michaelvincerra

This comment has been minimized.

Show comment
Hide comment
@michaelvincerra

michaelvincerra Jun 28, 2017

Hi Mike,
This is a very cool chart. Do you know if there's any way to replace the line referring to: d3.tsv("data.tsv", type, function(error, data)...
with a reference to a local variable, which represents a JSON object?

Anything helps.
Best,
Michael

michaelvincerra commented Jun 28, 2017

Hi Mike,
This is a very cool chart. Do you know if there's any way to replace the line referring to: d3.tsv("data.tsv", type, function(error, data)...
with a reference to a local variable, which represents a JSON object?

Anything helps.
Best,
Michael

@bunnyeatsgrass

This comment has been minimized.

Show comment
Hide comment
@bunnyeatsgrass

bunnyeatsgrass Aug 11, 2017

/Michael
You can debug inside of d3.tsv, and check the variable "data"
d3.tsv("data.tsv", type, function(error, data) <- this "data"
You can see how data is stored and mimic the structure from your local variable.
Then, strip off d3.tsv function and directly use your data variable for '.data(data)' statement.

bunnyeatsgrass commented Aug 11, 2017

/Michael
You can debug inside of d3.tsv, and check the variable "data"
d3.tsv("data.tsv", type, function(error, data) <- this "data"
You can see how data is stored and mimic the structure from your local variable.
Then, strip off d3.tsv function and directly use your data variable for '.data(data)' statement.

@arnab-dibosh

This comment has been minimized.

Show comment
Hide comment
@arnab-dibosh

arnab-dibosh Nov 30, 2017

Hi Mike,
Nice chart. This is helping me a lot. However there is an issue with the following data:
var data=[{name: "Change in Count Share", value: -20},
{name: "Change in Opp Share", value: -33.82819098737223},
{name: "Change in Price Opp Share", value: -11.062986880253305}]

The bars goes beyond the Chart in IE. Can you please have a look on this issue please?

arnab-dibosh commented Nov 30, 2017

Hi Mike,
Nice chart. This is helping me a lot. However there is an issue with the following data:
var data=[{name: "Change in Count Share", value: -20},
{name: "Change in Opp Share", value: -33.82819098737223},
{name: "Change in Price Opp Share", value: -11.062986880253305}]

The bars goes beyond the Chart in IE. Can you please have a look on this issue please?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment