forked from mbostock's block: Population Pyramid
forked from benduffin's block: Population Pyramid
forked from benduffin's block: Population Pyramid
xxxxxxxxxx
<meta charset="utf-8">
<style>
svg {
font: 10px sans-serif;
}
.y.axis path {
display: none;
}
.y.axis line {
stroke: #fff;
stroke-opacity: .2;
shape-rendering: crispEdges;
}
.y.axis .zero line {
stroke: #000;
stroke-opacity: 1;
}
.title {
font: 600 78px Arial;
fill: #334455;
}
.birthyear,
.age {
text-anchor: middle;
}
.birthyear {
fill: #fff;
}
rect {
fill-opacity: .6;
fill: #dd78bf;
mix-blend-mode: multiply;
}
rect:first-child {
fill: #1eb7ef;
}
///////////////////
.axis {
font: 10px sans-serif;
-webkit-user-select: none;
-moz-user-select: none;
user-select: none;
}
.axis .domain {
fill: none;
stroke: #000;
stroke-opacity: .3;
stroke-width: 5px;
}
.axis .halo {
fill: none;
stroke: #ddd;
stroke-width: 8px;
}
.slider .handle {
fill: #fff;
stroke: #000;
stroke-opacity: .5;
stroke-width: 1.25px;
cursor: pointer;
}
</style>
<body>
<script src="//d3js.org/d3.v3.min.js"></script>
<script>
var margin = {top: 40, right: 60, bottom: 40, left: 40},
width = 940 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom,
barWidth = Math.floor(width / 101);
///////////////////////////////////////////////////
var w = d3.scale.linear()
.domain([1986, 2046])
.range([0, width])
.clamp(true);
var brush = d3.svg.brush()
.x(w)
.extent([0, 0])
.on("brush", brushed);
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.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(d3.svg.axis()
.scale(w)
.orient("bottom")
.tickFormat(function(d) { return d; })
.tickSize(0)
.tickValues(d3.range(1986, 2047, 5))
.tickPadding(12))
.select(".domain")
.select(function() { return this.parentNode.appendChild(this.cloneNode(true)); })
.attr("class", "halo");
var slider = svg.append("g")
.attr("class", "slider")
.call(brush);
slider.selectAll(".extent,.resize")
.remove();
slider.select(".background")
.attr("height", height);
var handle = slider.append("circle")
.attr("class", "handle")
.attr("transform", "translate(0," + height + ")")
.attr("r", 9);
slider
.call(brush.extent([2046, 2046]))
.call(brush.event);
/////////////////////////////////////////////////////
var x = d3.scale.linear()
.range([barWidth / 2, width - barWidth / 2]);
var y = d3.scale.linear()
.range([height - 50, 0]);
var yAxis = d3.svg.axis()
.scale(y)
.orient("right")
.tickSize(-width)
.tickValues(d3.range(0, 250001, 50000))
.tickFormat(function(d) { return Math.round(d / 1e3) + "k"; });
// An SVG element with a bottom-right origin.
var shape = d3.select("svg")
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
// A sliding container to hold the bars by birthyear.
var birthyears = svg.append("g")
.attr("class", "birthyears");
d3.csv("population.csv", function(error, data) {
// Convert strings to numbers.
data.forEach(function(d) {
d.people = +d.people;
d.year = +d.year;
d.age = +d.age;
});
// Compute the extent of the data set in age and years.
var age1 = d3.max(data, function(d) { return d.age; }),
year0 = d3.min(data, function(d) { return d.year; }),
year1 = d3.max(data, function(d) { return d.year; }),
year = year1;
// Update the scale domains.
x.domain([year1 - age1, year1]);
y.domain([0, 250000]);
// Produce a map from year and birthyear to [male, female].
data = d3.nest()
.key(function(d) { return d.year; })
.key(function(d) { return d.year - d.age; })
.rollup(function(v) { return v.map(function(d) { return d.people; }); })
.map(data);
// Add an axis to show the population values.
svg.append("g")
.attr("class", "y axis")
.attr("transform", "translate(" + width + ",0)")
.call(yAxis)
.selectAll("g")
.filter(function(value) { return !value; })
.classed("zero", true);
// Add labeled rects for each birthyear (so that no enter or exit is required).
var birthyear = birthyears.selectAll(".birthyear")
.data(d3.range(year0 - age1, year1 + 1, 1))
.enter().append("g")
.attr("class", "birthyear")
.attr("transform", function(birthyear) { return "translate(" + x(birthyear) + ",0)"; });
birthyear.selectAll("rect")
.data(function(birthyear) { return data[year][birthyear] || [0, 0]; })
.enter().append("rect")
.attr("x", -barWidth / 2)
.attr("width", barWidth)
.attr("y", y)
.attr("height", function(value) { return height - 50 - y(value); });
// Add labels to show age (separate; not animated).
svg.selectAll(".age")
.data(d3.range(0, age1 + 1, 5))
.enter().append("text")
.attr("class", "age")
.attr("x", function(age) { return x(year - age); })
.attr("y", height - 45)
.attr("dy", ".71em")
.text(function(age) { return age; });
// A label for the current year.
var title = svg.append("text")
.attr("class", "title")
.attr("dy", "0.85em")
.text(2046);
// Allow the arrow keys to change the displayed year.
window.focus();
d3.select(window).on("keydown", function() {
switch (d3.event.keyCode) {
case 37: year = brush.extent()[0]; break;
case 39: year = Math.min(year1, year + 30); break;
}
update();
});
function update() {
if (!(year in data)) return;
title.text(year);
birthyears.transition()
.duration(750)
.attr("transform", "translate(" + (x(year1) - x(year)) + ",0)");
birthyear.selectAll("rect")
.data(function(birthyear) { return data[year][birthyear] || [0, 0]; })
.transition()
.duration(750)
.attr("y", y)
.attr("height", function(value) { return height - 50 - y(value); });
}
});
function brushed() {
var value = brush.extent()[0];
if (d3.event.sourceEvent) { // not a programmatic event
value = w.invert(d3.mouse(this)[0]);
brush.extent([value, value]);
}
switch (d3.event.keyCode) {
case 37: year = value; break;
case 39: year = Math.min(year1, year + 30); break;
}
handle.attr("cx", w(value));
d3.select(".title").text(Math.round(value));
}
</script>
https://d3js.org/d3.v3.min.js