Skip to content

Instantly share code, notes, and snippets.

@ndarville ndarville/README.md
Last active Dec 29, 2015

Embed
What would you like to do?
Symmetric Stack Chart

(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.

Future Features

  • Divider for 50% thresholds
  • Value labels
  • Column labels
Income Bracket Obama Romney
<$50000 60 38
$50000-90000 46 52
>=$100000 44 54
<!DOCTYPE html>
<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="http://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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.