Skip to content

Instantly share code, notes, and snippets.

@basilesimon
Last active August 29, 2019 12:51
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save basilesimon/5c24e23f77b084969cccb70699da4910 to your computer and use it in GitHub Desktop.
Save basilesimon/5c24e23f77b084969cccb70699da4910 to your computer and use it in GitHub Desktop.
Symmetric Stack Chart (v4)
license: mit

(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

forked from ndarville's block: Symmetric Stack Chart

Vote Labour Conservative Rebels SNP
May 266 253 0 4
Johnson 250 258 10 15
<!DOCTYPE html>
<meta charset="utf-8">
<title>Bar Chart with Negative Values</title>
<style>
svg {
margin-left: auto;
margin-right: auto;
display: block;
overflow: visible;
}
.bar.right {
fill: #75a5d3;
}
.bar.rebels {
fill: #2c5d8b;
}
.bar.left {
fill: #bb5345;
}
.bar.snp {
fill: #f2cf73;
}
.axis text {
font: 14px sans-serif;
fill: white;
}
.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.v4.min.js"></script>
<script src="http://unpkg.com/d3-jetpack"></script>
<script>
var margin = { top: 30, right: 10, bottom: 10, left: 10 },
width = 960 - (margin.left + margin.right),
height = 900 - (margin.top + margin.bottom);
(width = width - 200), (height = height - 500);
(headerColumn = 'Vote'),
(leftColumn = 'Conservative'),
(rebelsColumn = 'Rebels'),
(rightColumn = 'Labour');
snpColumn = 'SNP';
var x = d3
.scaleBand()
.range([0, height])
.padding(0.4);
var y = d3.scaleLinear().range([0, width]);
var xAxis = d3
.axisBottom(x)
.ticks(7)
.tickSizeOuter(0)
.tickSizeInner(0)
.tickPadding(8);
var svg = d3
.select('body')
.append('svg')
.at({
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])));
});
y.domain(
// Include other column
[
-d3.max(data, function(d) {
return d[leftColumn];
}),
d3.max(data, function(d) {
return d[leftColumn];
}),
]
).nice();
x.domain(
data.map(function(d) {
return d[headerColumn];
})
);
var bar = svg
.selectAll('.bar')
.data(data)
.enter();
// Right bar
bar.append('rect').at({
class: 'bar right',
y: y(0),
x: function(d) {
return x(d[headerColumn]);
},
height: function(d) {
return Math.abs(y(d[rightColumn]) - y(0));
},
width: x.bandwidth(),
});
// Left bar
bar.append('rect').at({
class: 'bar rebels',
y: function(d) {
return y(-d[rebelsColumn]);
},
x: function(d) {
return x(d[headerColumn]);
},
height: function(d) {
return Math.abs(y(d[rebelsColumn]) - y(0));
},
width: x.bandwidth(),
});
// Left bar
bar.append('rect').at({
class: 'bar left',
y: function(d) {
return y(-d[leftColumn]) - Math.abs(y(d[rebelsColumn]) - y(0));
},
x: function(d) {
return x(d[headerColumn]);
},
height: function(d) {
return Math.abs(y(d[leftColumn]) - y(0));
},
width: x.bandwidth(),
});
// Left bar
bar.append('rect').at({
class: 'bar snp',
y: function(d) {
return y(-d[snpColumn]) - Math.abs(y(d[leftColumn]) - y(0));
},
x: function(d) {
return x(d[headerColumn]);
},
height: function(d) {
return Math.abs(y(d[snpColumn]) - y(0));
},
width: x.bandwidth(),
});
svg.append('g')
.attr('class', 'x axis')
.call(xAxis)
.translate([0, y(0)]);
});
</script>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment