|
<!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> |