|
<!DOCTYPE html> |
|
<html> |
|
<head> |
|
<meta charset="utf-8"/> |
|
<title>選舉中因得票率未達標準不予返還的保證金數目(2004 ~ 2012)</title> |
|
<script src="//d3js.org/d3.v3.min.js"></script> |
|
<script src="//labratrevenge.com/d3-tip/javascripts/d3.tip.v0.6.3.js"></script> |
|
</head> |
|
<body> |
|
<div id="canvas"> |
|
</div> |
|
<script> |
|
function confiscatedChart() { |
|
|
|
var margin = { left: 100, top: 50, right: 180, bottom: 30 }; |
|
var spacing = 20; |
|
var width = 960 - margin.left - margin.right, |
|
height = 500 - margin.top - margin.bottom; |
|
|
|
var xScale = d3.scale.ordinal() |
|
.rangeRoundBands([0, width], .1); |
|
var yScale = d3.scale.linear() |
|
.range([height, 0]); |
|
|
|
var positions = [ "總統", "立法委員", "縣巿長", "縣巿議員", "直轄巿巿長", "直轄巿議員", "直轄巿里長", "任務型國大代表" ]; |
|
var color = d3.scale.category20().domain(positions); |
|
|
|
var xAxis = d3.svg.axis() |
|
.orient("bottom") |
|
.scale(xScale); |
|
var yAxis = d3.svg.axis() |
|
.orient("left") |
|
.scale(yScale); |
|
|
|
var barTip = d3.tip() |
|
.attr("class", "tip") |
|
.offset([-10, 0]) |
|
.html(function (d) { |
|
return '<div class="title">' + d.position + '</div><span>' + d.people_count + '人 ' + d.amount + '元</span>'; |
|
}); |
|
|
|
var lineTip = d3.tip() |
|
.attr("class", "tip") |
|
.offset([4, 0]) |
|
.html(function (d) { |
|
return '<div class="title">' + d.year + '年</div><span>累計 ' + d.sum + ' 元</span>'; |
|
}); |
|
|
|
var stack = d3.layout.stack() |
|
.values(function (d) { return d.values; }) |
|
.x(function (d) { return d.year; }) |
|
.y(function (d) { return d.amount; }); |
|
|
|
function joinData(done) { |
|
d3.csv('deposit.csv', function (depositData) { |
|
d3.csv('confiscated.csv', function (confisData) { |
|
confisData.forEach(function (d) { |
|
confisData[d.cec_id] = d; |
|
}); |
|
var data = {}; |
|
data.entries = depositData.map(function (d) { |
|
d.amount = +d.amount * +confisData[d.cec_id].people_count; |
|
d.year = +d.year; |
|
d.people_count = +confisData[d.cec_id].people_count; |
|
return d; |
|
}).sort(function (a, b) { |
|
return a.year - b.year; |
|
}); |
|
data.yearRange = [ |
|
d3.min(data.entries, function (d) { return d.year; }), |
|
d3.max(data.entries, function (d) { return d.year; }) + 1 |
|
]; |
|
var sum = 0; |
|
data.sum = d3.range(data.yearRange[0], data.yearRange[1]).map(function (year) { |
|
var amount = data.entries |
|
.filter(function (d) { return d.year == year; }) |
|
.reduce(function (prev, v) { return prev + v.amount; }, 0); |
|
sum += amount; |
|
return { |
|
year: year, |
|
amount: amount, |
|
prevSum: sum - amount, |
|
sum: sum |
|
}; |
|
}); |
|
done(data); |
|
}); |
|
}); |
|
} |
|
|
|
function buildLayers(data) { |
|
var layers = []; |
|
positions.forEach(function (pos) { |
|
var values = data.entries.filter(function (d) { return d.position === pos; }); |
|
i = 0; |
|
for (var year = data.yearRange[0]; year < data.yearRange[1]; year++) { |
|
if (values[i] === undefined || values[i].year !== year) { |
|
values.splice(i, 0, { |
|
"year": year, |
|
"position": pos, |
|
"amount": 0, |
|
"currency": "NTD" |
|
}); |
|
} |
|
++i; |
|
}; |
|
layers.push({ |
|
"position": pos, |
|
"values": values |
|
}); |
|
}); |
|
return layers; |
|
} |
|
|
|
function drawBarChart(selection, data) { |
|
var layers = stack(buildLayers(data)); |
|
var layer = selection.selectAll("g.layer") |
|
.data(layers) |
|
.enter().append("g") |
|
.attr("class", "layer") |
|
.attr("fill", function (d) { return color(d.position); }); |
|
var rect = layer.selectAll("rect.year") |
|
.data(function (d) { return d.values; }) |
|
.enter().append("rect") |
|
.attr("class", function (d) { return "year " + d.year; }) |
|
.attr("x", function (d) { return xScale(d.year); }) |
|
.attr("y", function (d) { return yScale(d.y0 + d.y); }) |
|
.attr("width", xScale.rangeBand()) |
|
.attr("height", function (d) { return yScale(d.y0) - yScale(d.y0 + d.y); }) |
|
.on("mouseover", barTip.show) |
|
.on("mouseout", barTip.hide); |
|
}; |
|
|
|
function drawLineChart(selection, data) { |
|
var lines = selection.append("g") |
|
.attr("class", "sum"); |
|
var sum = data.sum.slice().splice(1); |
|
var line = lines.selectAll("line.sum") |
|
.data(sum) |
|
.enter().append("line") |
|
.attr("x1", function (d) { return xScale(d.year - 1); }) |
|
.attr("y1", function (d) { return yScale(d.prevSum); }) |
|
.attr("x2", function (d) { return xScale(d.year); }) |
|
.attr("y2", function (d) { return yScale(d.sum); }) |
|
.attr("transform", "translate(" + (xScale.rangeBand() * 0.5) + ",0)"); |
|
var dot = lines.selectAll("g.sum") |
|
.data(data.sum) |
|
.enter().append("g") |
|
.attr("class", "sum") |
|
.attr("transform", "translate(" + (xScale.rangeBand() * 0.5) + ",0)"); |
|
dot.append("circle") |
|
.attr("class", "sum") |
|
.attr("cx", function (d) { return xScale(d.year); }) |
|
.attr("cy", function (d) { return yScale(d.sum); }) |
|
.attr("r", 2); |
|
dot.append("circle") |
|
.attr("class", "track") |
|
.attr("cx", function (d) { return xScale(d.year); }) |
|
.attr("cy", function (d) { return yScale(d.sum); }) |
|
.attr("r", 16) |
|
.on("mouseover", lineTip.show) |
|
.on("mouseout", lineTip.hide); |
|
}; |
|
|
|
function drawAxis(selection) { |
|
selection.append("g") |
|
.attr("class", "x axis") |
|
.attr("transform", "translate(0," + height +")") |
|
.call(xAxis) |
|
.append("text") |
|
.attr("transform", "translate(" + width + ",16)") |
|
.style("text-anchor", "middle") |
|
.text("年"); |
|
selection.append("g") |
|
.attr("class", "y axis") |
|
.call(yAxis) |
|
.append("text") |
|
.text("金額") |
|
.attr("transform", "translate(-8,4)") |
|
.style("text-anchor", "end"); |
|
} |
|
|
|
function draw(selection) { |
|
var svg = selection.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 + ")") |
|
.call(barTip) |
|
.call(lineTip); |
|
joinData(function (data) { |
|
console.log(data); |
|
xScale.domain(d3.range(data.yearRange[0], data.yearRange[1])); |
|
yScale.domain([0, d3.max(data.sum, function (d) { return d.sum; }) * 1.1]); |
|
|
|
drawBarChart(svg, data); |
|
drawLineChart(svg, data); |
|
drawAxis(svg); |
|
}); |
|
|
|
return draw; |
|
} |
|
|
|
draw.width = function (value) { |
|
if (!arguments.length) { return width; } |
|
width = value; |
|
return draw; |
|
}; |
|
|
|
draw.height = function (value) { |
|
if (!arguments.length) { return height; } |
|
height = value; |
|
return draw; |
|
}; |
|
|
|
draw.margin = function (value) { |
|
if (!arguments.length) { return margin; } |
|
margin = value; |
|
return draw; |
|
} |
|
|
|
draw.legend = function (selection) { |
|
var legend = selection.append("div") |
|
.attr("id", "legend") |
|
.attr("style", "bottom: " + margin.bottom + "px; right: " + spacing + "px; width: " + (margin.right - margin.left * 3) + "px;"); |
|
legend.selectAll("div") |
|
.data(positions) |
|
.enter().append("div") |
|
.attr("style", function (d) { return "background-color: " + color(d) + "; padding: 5px;"; }) |
|
|
|
.text(function (d) { return d; }); |
|
return draw; |
|
}; |
|
|
|
draw.caption = function (selection) { |
|
var caption = selection.append("div") |
|
.text("歷年選舉被沒收的保證金(2004 年~2014 年)") |
|
.attr("class", "caption") |
|
.attr("style", "position: absolute; top: " + spacing + "px; left: " + (width / 2) + "px;"); |
|
}; |
|
|
|
return draw; |
|
} |
|
|
|
var chart = confiscatedChart(); |
|
d3.select("#canvas") |
|
.call(chart) |
|
.call(chart.legend) |
|
.call(chart.caption); |
|
|
|
</script> |
|
<style> |
|
body { |
|
margin: 0; |
|
padding: 0; |
|
} |
|
#canvas { |
|
position: relative; |
|
width: 960px; |
|
height: 500px; |
|
} |
|
#canvas rect { |
|
shape-rendering: crispEdges; |
|
} |
|
#canvas rect:hover { |
|
fill: rgba(0, 0, 0, 0.5); |
|
} |
|
#legend { |
|
position: absolute; |
|
} |
|
#canvas .sum line { |
|
stroke: #000; |
|
stroke-width: 1; |
|
} |
|
#canvas .sum circle.sum { |
|
stroke: #000; |
|
stroke-width: 1; |
|
fill: #fff; |
|
} |
|
#canvas .sum circle.track { |
|
fill-opacity: 0; |
|
stroke-opacity: 0; |
|
} |
|
#canvas .axis text { |
|
font: 10px sans-serif; |
|
} |
|
#canvas .axis path, |
|
#canvas .axis line { |
|
fill: none; |
|
stroke: #000; |
|
shape-rendering: crispEdges; |
|
} |
|
.tip { |
|
line-height: 1; |
|
padding: 12px; |
|
background: rgba(0, 0, 0, 0.8); |
|
color: #fff; |
|
border-radius: 2px; |
|
font: 10px sans-serif; |
|
} |
|
.tip .title { |
|
font-weight: bold; |
|
font: 12px sans-serif; |
|
} |
|
.tip:after { |
|
box-sizing: border-box; |
|
display: inline; |
|
font-size: 10px; |
|
width: 100%; |
|
line-height: 1; |
|
color: rgba(0, 0, 0, 0.8); |
|
content: "\25BC"; |
|
position: absolute; |
|
text-align: center; |
|
} |
|
.tip.n:after { |
|
margin: -1px 0 0 0; |
|
top: 100%; |
|
left: 0; |
|
} |
|
</style> |
|
</body> |
|
</html> |