Skip to content

Instantly share code, notes, and snippets.

@austinczarnecki
Last active March 16, 2016 12:04
Show Gist options
  • Save austinczarnecki/f8c71dd06c67ecbce840 to your computer and use it in GitHub Desktop.
Save austinczarnecki/f8c71dd06c67ecbce840 to your computer and use it in GitHub Desktop.
D3 horizontal bar chart with extras
license: gpl-3.0

This is a horizontal bar chart showing independent expenditures* on candidates in the 2016 US primaries. The colored bars represent independent spending for and against, while the thin grey bars represent money spent directly by the candidate committees. The data is up to date as of Wednesday, March 16, 2016.

This was built with D3 and the d3.tip plugin.

*individuals and non-candidate PACs

{"Clinton, Hillary":{"name":"Clinton, Hillary","support":9621130,"oppose":-4799577,"party":"dem","selfspend":97505073},"Sanders, Bernie":{"name":"Sanders, Bernie","support":2204077,"oppose":-829318,"party":"dem","selfspend":81649472},"Cruz, Ted":{"name":"Cruz, Ted","support":15387556,"oppose":-8132939,"party":"rep","selfspend":41016086},"Trump, Donald":{"name":"Trump, Donald","support":370002,"oppose":-38102173,"party":"rep","selfspend":23941598},"Rubio, Marco":{"name":"Rubio, Marco","support":45451144,"oppose":-7958144,"party":"rep","selfspend":32935702},"Kasich, John":{"name":"Kasich, John","support":12971972,"oppose":-5215774,"party":"rep","selfspend":7172247},"Bush, Jeb":{"name":"Bush, Jeb","support":83605848,"oppose":-3680598,"party":"rep","selfspend":30632058},"Average":{"name":"Average","support":28268622,"oppose":-11453087,"party":"none","selfspend":44978891}}
<!DOCTYPE html>
<meta charset="utf-8">
<link rel="stylesheet" type="text/css" href="style.css">
<body>
<script src="//d3js.org/d3.v3.min.js"></script>
<script src="//d3js.org/queue.v1.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.2.0/jquery.min.js"></script>
<script type="text/javascript" src="https://cdn.rawgit.com/Caged/d3-tip/master/index.js"></script>
<script>
var margin = {top: 50, right: 20, bottom: 20, left: 20};
var width = 950 - margin.right - margin.left,
height = 400 - margin.top - margin.bottom;
var x = d3.scale.linear()
.range([0, width]);
var y = d3.scale.ordinal()
.rangeRoundBands([0, height], 0.1);
var formatter = d3.format('s');
var xAxis = d3.svg.axis()
.scale(x)
.tickFormat(function (d) {
if (d === 0) return ''; // No label for '0'
else if (d < 0) d = -d; // No nagative labels
return formatter(d); })
.orient("top");
var yAxis = d3.svg.axis()
.scale(y)
.orient("left")
.tickSize(0)
.tickPadding(6);
var tip = d3.tip()
.attr('class', 'd3-tip')
.offset([0,0]);
var tipOffset = 155;
d3.json("data.json", function(error, data) {
x.domain(d3.extent([
d3.min(d3.entries(data), getOppose),
d3.max([d3.max(d3.entries(data), getSupport), d3.max(d3.entries(data), getSelfspend)])
])).nice();
y.domain(d3.keys(data));
x.range([0, width]);
y.rangeRoundBands([0, height], 0.1);
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 + ")");
svg.call(tip)
// plot opposing spending
svg.selectAll(".bar bar--negative")
.data(d3.entries(data))
.enter().append("rect")
.attr("class", function(d) { return "bar bar--negative " + d.value.party; })
.attr("x", function(d) { return x(Math.min(0, d.value.oppose)); })
.attr("y", function(d) { return y(d.value.name); })
.attr("width", function(d) { return Math.abs(x(d.value.oppose) - x(0)); })
.attr("height", y.rangeBand())
.on('mousemove', function(event) {
tip.style("top", (d3.event.pageY - tipOffset) + "px")
.style("left", (d3.event.pageX - tipOffset) + "px") })
.on('mouseover', tip.show)
.on('mouseout', tip.hide);
// plot supporting spending
svg.selectAll(".bar bar--positive")
.data(d3.entries(data))
.enter().append("rect")
.attr("class", function(d) { return "bar bar--positive " + d.value.party; })
.attr("x", function(d) { return x(Math.min(0, d.value.support)); })
.attr("y", function(d) { return y(d.value.name); })
.attr("width", function(d) { return Math.abs(x(d.value.support) - x(0)); })
.attr("height", y.rangeBand())
.on('mousemove', function(event) {
tip.style("top", (d3.event.pageY - tipOffset) + "px")
.style("left", (d3.event.pageX - tipOffset) + "px") })
.on('mouseover', tip.show)
.on('mouseout', tip.hide);
// plot black line for candidate's own spending
svg.selectAll(".bar candidate-spending")
.data(d3.entries(data))
.enter().append("rect")
.attr("class", "bar candidate-spending")
.attr("x", x(0))
.attr("y", function(d) { return y(d.value.name) + (y.rangeBand() / 21) * 10 })
.attr("width", function(d) { return x(d.value.selfspend) - x(0) })
.attr("height", y.rangeBand() / 21);
// plot line cap for candidate's own spending
svg.selectAll(".bar candidate-spending")
.data(d3.entries(data))
.enter().append("rect")
.attr("class", "bar candidate-spending")
.attr("x", function(d) { return x(d.value.selfspend) })
.attr("y", function(d) { return y(d.value.name) + (y.rangeBand() / 21) * 7 })
.attr("width", y.rangeBand() / 21)
.attr("height", (y.rangeBand() / 21) * 7);
var avg = d3.entries(data)
svg.selectAll(".bar candidate-spending")
.data([data.Average])
.enter().append("text")
.attr("class", "bar average-label-text")
.attr("x", function(d) {
return x(d.selfspend) + 10 })
.attr("y", function(d) { return y(d.name) + 8 })
.attr("dy", "1em")
.attr("text-anchor", "start")
.attr("alignment-baseline", "middle")
.style("opacity", 0.7)
.text("Candidate spending");
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0,0)")
.call(xAxis);
svg.append("g")
.attr("class", "y axis")
.attr("transform", "translate(" + x(0) + ",0)")
.call(yAxis);
svg.append("text")
.attr("y", -40)
.attr("x", x(d3.min(x.domain()) / 2))
.attr("dy", "1em")
.style("text-anchor", "middle")
.style("font-weight", "bold")
.text("Spending in opposition");
svg.append("text")
.attr("y", -40)
.attr("x", x(d3.max(x.domain()) / 2))
.attr("dy", "1em")
.style("text-anchor", "middle")
.style("font-weight", "bold")
.text("Spending in support");
});
function getOppose(d) { return d.value.oppose; }
function getSupport(d) { return d.value.support; }
function getSelfspend(d) { return d.value.selfspend; }
function formatAmount(val) {
return val.toLocaleString("en-US", {style: 'currency', currency: "USD"}).replace(/\.[0-9]+/, "");
};
tip.html(function(d) {
var html = '<div class="table-wrapper">'+
'<h1>'+d.value.name+'</h1>'+
'<table>'+
'<tr>'+
'<td class="col-left"><h2>Outside spending</h2></td>'+
'</tr>'+
'<tr>'+
'<td class="col-left">In support</td>'+
'<td align="right">'+formatAmount(d.value.support)+'</td>'+
'</tr>'+
'<tr>'+
'<td class="col-left">In opposition</td>'+
'<td align="right">'+formatAmount(Math.abs(d.value.oppose))+'</td>'+
'</tr>'+
'<tr>'+
'<td class="col-left">Total outside spending</td>'+
'<td align="right">'+formatAmount(Math.abs(d.value.oppose) + d.value.support)+'</td>'+
'</tr>'+
'<tr>'+
'<td class="col-left"><h2>Official candidate spending</h2></td>'+
'</tr>'+
'<tr>'+
'<td class="col-left">Total candidate spending</td>'+
'<td align="right">'+formatAmount(d.value.selfspend)+'</td>'+
'</tr>'+
'</table>'+
'</div>';
return html;
});
</script>
body {
font-family: -apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Oxygen,Ubuntu,Cantarell,"Open Sans","Helvetica Neue",sans-serif;
}
svg text {
font-size: 12px;
stroke-width: 0;
fill: black;
}
.axis text {
font: 10px sans-serif;
}
.axis path,
.axis line {
fill: none;
stroke: #000;
shape-rendering: crispEdges;
pointer-events: none;
}
.tick {
pointer-events:none;
}
rect.dem {
fill: #3366ff;
stroke: #3366ff;
}
rect.dem:hover {
fill: #0040ff;
stroke: #0040ff;
}
rect.rep {
fill: #ff0000;
stroke: #ff0000;
}
rect.rep:hover {
fill: #cc0000;
stroke: #cc0000;
}
rect.none {
fill: #669999;
stroke: #669999;
}
rect.none:hover {
fill: #527a7a;
stroke: #527a7a;
}
rect.bar.bar--positive.none {
fill: rgb(102, 153, 153);
stroke: rgb(102, 153, 153);
fill-opacity: 0.01;
stroke-dasharray: 3;
}
rect.bar.bar--positive.none:hover {
fill-opacity: 0.1;
}
rect.bar.bar--negative.dem {
fill: #99b3ff;
stroke: #99b3ff;
}
rect.dem.bar--negative:hover {
fill: #668cff;
stroke: #668cff;
}
rect.rep.bar--negative {
fill: #ff6666;
stroke: #ff6666;
}
rect.rep.bar--negative:hover {
fill: #ff3333;
stroke: #ff3333;
}
rect.none.bar--negative {
fill: rgb(102, 153, 153);
stroke: rgb(102, 153, 153);
fill-opacity: 0.01;
stroke-dasharray: 3;
}
rect.none.bar--negative:hover {
fill-opacity: 0.1;
}
.bar--positive.other {
fill: #669999;
stroke: #669999;
}
.bar--positive.other:hover {
fill: #527a7a;
stroke: #527a7a;
}
.bar--negative.other {
fill: #c2d6d6;
stroke: #c2d6d6;
}
.bar--negative.other:hover {
fill: #a3c2c2;
stroke: #a3c2c2;
}
.candidate-spending {
fill: black;
opacity: 0.5;
pointer-events: none;
}
/* tooltip styles */
.d3-tip h1 {
font-weight: 500;
font-size: 14px;
padding: 0;
margin-top: 0;
margin-bottom: 5px;
width: 100%;
}
.d3-tip h2 {
font-weight: bold;
font-size: 12px;
padding-right: inherit;
padding-left: inherit;
padding-top: 2px;
padding-bottom: 2px;
margin: 0px;
}
.d3-tip h3 {
font-weight: normal;
font-size: 8px;
margin: 0;
padding: 0;
}
.d3-tip table {
font-weight: normal;
font-size: 12px;
padding: none;
margin: 0;
width: 100%;
border: none;
border-collapse: collapse;
}
.d3-tip td {
padding-top: 2px;
padding-bottom: 2px;
}
.d3-tip .col-left {
padding-right: 8px;
}
.d3-tip .table-wrapper {
margin: 0;
padding: inherit;
border: none;
}
.d3-tip {
line-height: 1;
font-weight: normal;
padding: 4px;
background: white;
color: black;
border-radius: 2px;
pointer-events: none;
background: white;
box-shadow: 1px 1px 4px grey;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment