(This chart is a part of the d3-charts
collection available here.)
This is an symmetric stack chart with each bar anchored around a common origin. The code was based on Mike Bostock's "Bar Chart with Negative Values" chart and inspired by a post on Junk Charts.
Unlike the examples in the two links, the x axis of this implementation has a symmetric x-axis to ensure readers aren't perceptually manipulated by one side taking up a larger, more imposing, share of the space than the other.
The main thing to have in mind when using this particular chart is that the order of your data rows matter; a part of the story this chart tells uses the vertical dimension, so embrace it. Usually, you'll see people use the chart for demographics with people divided by age or income, but there are plenty of other uses you'll realize soon enough.
The placeholder data shows how Americans voted by income brackets in the 2012 election.
xxxxxxxxxx
<meta charset="utf-8">
<title>Bar Chart with Negative Values</title>
<style>
svg {
margin-left: auto;
margin-right: auto;
display: block;
}
.bar.right {
fill: steelblue;
}
.bar.left {
fill: brown;
}
.axis text {
font: 10px sans-serif;
}
.axis path,
.axis line {
fill: none;
stroke: #000;
shape-rendering: crispEdges;
}
.grid .tick {
stroke: #FFF;
opacity: 0.7;
stroke-width: 0.5;
}
.grid path {
stroke-width: 0;
}
</style>
<body>
<script src="https://d3js.org/d3.v3.min.js"></script>
<script>
var margin = {top: 30, right: 10, bottom: 10, left: 10},
width = 960 - (margin.left + margin.right),
height = 500 - (margin.top + margin.bottom);
width = width-520,
height = height-200;
headerColumn = "Income Bracket",
leftColumn = "Obama",
rightColumn = "Romney";
var x = d3.scale.linear()
.range([0, width])
var y = d3.scale.ordinal()
.rangeRoundBands([0, height], .2);
var xAxis = d3.svg.axis()
.scale(x)
.ticks(7)
// Positive values on both sides
.tickFormat(function(d) { return (d<0) ? -1*d+"%" : d+"%"; })
.orient("top");
var svg = d3.select("body").append("svg")
.attr({
"width": width + margin.left + margin.right,
"height": height + margin.top + margin.bottom
})
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
d3.csv("data.csv", function(error, data) {
data.forEach(function(d) {
d[leftColumn] = parseFloat(Math.abs(d[leftColumn])),
d[rightColumn] = parseFloat(Math.abs(d[rightColumn]));
});
x.domain(
// Include other column
[-d3.max(data, function(d) { return d[leftColumn]; }),
d3.max(data, function(d) { return d[leftColumn]; })]
).nice();
y.domain(data.map(function(d) { return d[headerColumn]; }));
var bar = svg.selectAll(".bar").data(data).enter()
// Right bar
bar.append("rect")
.attr({
"class": "bar right",
"x": x(0),
"y": function(d) { return y(d[headerColumn]); },
"width": function(d) {
return Math.abs(x(d[rightColumn]) - x(0)); },
"height": y.rangeBand()
});
// Left bar
bar.append("rect")
.attr({
"class": "bar left",
"x": function(d) { return x(-d[leftColumn]); },
"y": function(d) { return y(d[headerColumn]); },
"width": function(d) {
return Math.abs(x(d[leftColumn]) - x(0)); },
"height": y.rangeBand()
});
svg.append("g")
.attr("class", "x axis")
.call(xAxis);
svg.append("g")
.attr("class", "y axis")
.append("line")
.attr({
"x1": x(0),
"x2": x(0),
"y2": height
});
/** TODO: Labels/Tooltips */
svg.append("g")
.attr({
"class": "grid",
"transform": "translate(0," + height + ")"
})
.call(xAxis
.tickSize(height, 0, 0)
.tickFormat("")
);
});
</script>
Modified http://d3js.org/d3.v3.min.js to a secure url
https://d3js.org/d3.v3.min.js