xxxxxxxxxx
<html>
<head>
<title>Dynamic Population Pyramid</title>
<link rel="stylesheet" type="text/css" href="style.css">
<link href="nouislider.css" rel="stylesheet">
</head>
<body>
<div id="slider"></div>
<div id="tooltip" class="hidden"><p><span id="value"></p></div>
<script src="lodash.js"></script>
<script src="https://d3js.org/d3.v4.min.js"></script>
<script src="https://d3js.org/d3-scale.v1.min.js"></script>
<script src="https://d3js.org/d3-drag.v1.min.js"></script>
<script src="nouislider.min.js"></script>
<script>
// Margin Convention
var margin = {top: 60, right: 20, bottom: 50, left: 20},
padding = {top: 0, right: 0, bottom: 0, left: 0},
vizWidth = 350,
vizHeight = 500,
plotWidth = vizWidth - margin.left - margin.right,
plotHeight = vizHeight - margin.top - margin.bottom,
panelWidth = plotWidth - padding.left - padding.right,
panelHeight = plotHeight - padding.top - padding.bottom;
var viz = d3.select("body").append("svg")
.attr("id", "viz")
.attr("width", vizWidth)
.attr("height", vizHeight);
viz.append("text")
.attr("id","title")
.attr("x",vizWidth/2)
.attr("y",margin.top/2)
.attr("text-anchor","middle")
.attr("alignment-baseline","c")
.text("SINGAPORE'S RESIDENT DEMOGRAPHY")
var plot = viz.append("g")
.attr("id","plot")
.attr("transform",
"translate(" + margin.left + "," + margin.top + ")");
var panel = plot.append("g")
.attr("id","panel")
.attr("transform", "translate(" + padding.left + "," + padding.top + ")");
//button drawing
//the class of the button is the what happened to the animation after you
//press the button
var buttonHeight = 15, buttonWidth = 40;//hard coding
var button = viz.append("g")
.attr("id","button")
.classed("play",true)
.attr("transform", "translate(" + (margin.left) + "," +
(margin.top+panelHeight - buttonHeight)+ ")");
button.append("rect")
.attr("height",buttonHeight)
.attr("width",buttonWidth)
.attr("fill","white")
.on("mouseover",function(){d3.select(this).attr("fill","grey")})
.on("mouseout",function(){d3.select(this).attr("fill","white")})
.style("stroke","black");
button.append("text")
.attr("x",buttonWidth/2)
.attr("y",buttonHeight/2)
.attr("text-anchor","middle")
.attr("alignment-baseline","middle")
.text("PLAY") //since the starting class is play
//displayYear
var displayYear = viz.append("text")
.attr("id","displayYear")
.attr("transform","translate("+margin.left+","+margin.top+ ")")
.attr("text-anchor","start")
.attr("alignment-baseline","baseline")
//slider drawing
var sliderDiv = d3.select("div#slider")
.style("left",margin.left +"px")
.style("top",(vizHeight-margin.bottom/2)+"px")
.style("width",panelWidth+"px")
.style("height",margin.left/2 +"px")
//Add Male Logo
viz.append("g")
.attr("id","maleLogo")
d3.html("male.svg", function(svgData){
d3.select("#maleLogo").node().appendChild(d3.select(svgData).select("path").node())
})
d3.select("#maleLogo")
.attr("transform","translate("+margin.left+","+ (margin.top+margin.left)+") scale(0.1,0.1)")
//Add female logo
viz.append("g")
.attr("id","femaleLogo")
d3.html("female.svg",function(svgData){
d3.select("#femaleLogo").node().appendChild(d3.select(svgData).select("path").node())
})
d3.select("#femaleLogo")
.attr("transform","translate("+(vizWidth-2*margin.right)+","+ (margin.top+margin.left)+") scale(0.1,0.1)")
//Data binding
d3.csv("population pyramid 20170913.csv", function(population) {
for (var i=0; i<population.length; i++){
population[i].start_age = parseInt(population[i]["start_age"]);
};
population = _.sortBy(population,["start_age"]);
var male = _.filter(population,["Sex","Males"]),
female = _.filter(population,["Sex","Females"]),
allPop = Object.assign([],population);
var years = _.keys(population[0]);
let valuesToRemove = ["Sex","AG","start_age"];
years = years.filter((o) => !valuesToRemove.includes(o)),
years = years.map((o)=> parseInt(o));
var allPop = population.map(o => _.omit(o,valuesToRemove)),
allPop = allPop.map(function(foo){return parseInt(Object.values(foo));}),
allPop = [].concat.apply([], allPop);
var index = 0;
displayYear.text(years[index]);
var maxPop = d3.max(allPop);
var scaleWidth = d3.scaleLinear().range([0,panelWidth/2]).domain([0,maxPop]),
height = panelHeight/(population.length/2);
var group = panel.append("g")
.attr("transform", "translate(" + panelWidth/2 + "," +
panelHeight + ") scale(" + 1 + "," + -1 + ")");
var malePanel = group.append("g").attr("transform", "scale(" + -1 + "," + 1 + ")")
var maleRect = malePanel.selectAll("rect")
.data(male)
.enter()
.append("rect")
.attr("id","male")
.attr("width",function(d,i){return scaleWidth(d[years[index]]);})
.attr("height",height)
.attr("y",function(d,i){return i*height;})
.attr("x",0)
.on('mouseover', function(d) {
d3.select(this).classed("highlight",true);
drawTooltip(d,years[index]);}) // call tooltip function
.on('mouseout',function(){
d3.select("#tooltip").classed("hidden", true);
d3.select(this).classed("highlight",false);
});
malePanel.append("g")
.classed("maleAxis",true)
.attr("transform", "scale(" + 1 + "," + -1 + ")")
.call(d3.axisTop(scaleWidth).tickSize(panelHeight).tickArguments([3]))
viz.append("text")
.attr("id","yTitle")
.attr("transform", "translate(" +(vizWidth/2 -2) + "," +
(margin.top + panelHeight-2)+ ")")
.text("AGE")
var femalePanel = group.append("g")
var femaleRect = femalePanel.selectAll("rect")
.data(female)
.enter()
.append("rect")
.attr("id","female")
.attr("width",function(d,i){return scaleWidth(d[years[index]]);})
.attr("height",height)
.attr("y",function(d,i){return i*height;})
.attr("x",0)
.on('mouseover', function(d) {
d3.select(this).classed("highlight",true);
drawTooltip(d,years[index]);}) // call tooltip function
.on('mouseout',function(){
d3.select("#tooltip").classed("hidden", true);
d3.select(this).classed("highlight",false);
});
femalePanel.append("g")
.classed("femaleAxis",true)
.attr("transform", "scale(" + 1 + "," + -1 + ")")
.call(d3.axisTop(scaleWidth).tickSize(panelHeight).tickArguments([3]))
var yVal = male.map(o => o.start_age);
var yAxis = femalePanel.append("g")
yAxis.selectAll("text")
.data(yVal)
.enter()
.append("text")
.attr("class","yAxis")
.attr("transform", "scale(" + 1 + "," + -1 + ") translate(" + 2 + "," + -2 + ")")
.attr("x",0)
.attr("y",function(d,i){return -i*height;})
.text(function(d){return d.toString()})
//Tooltip drawing
function drawTooltip(d,currYear) {
var xPosition = d3.event.pageX;
var yPosition = d3.event.pageY;
d3.select("#tooltip")
.classed("hidden",false)
.style("left", xPosition + "px")
.style("top", yPosition + "px")
.html("Year: " + currYear + "<br>" +
"Sex: " + d.Sex + "<br>" +
"Age: "+ d.start_age+ " to "+(d.start_age+4) + "<br>"+
"Total: " + d[currYear]);
}
function updateWidth(index){
maleRect.transition()
.attr("width",function(d,i){return scaleWidth(d[years[index]]);})
femaleRect.transition()
.attr("width",function(d,i){return scaleWidth(d[years[index]]);})
}
function updateButton(oldClass, newClass){
button.classed(oldClass,false)
button.classed(newClass,true)
d3.select("#button text")
.transition()
.text(newClass.toUpperCase())
}
function updateDisplayYear(index){
displayYear.transition()
.text(years[index])
}
var slider = document.getElementById('slider');
noUiSlider.create(slider, {
start: [ years[0] ],
step:1,
range: {'min': [ years[0] ],
'max': [ years[years.length-1] ]},
pips: {mode: 'count',values: 5,density :6}
});
d3.select("div.noUi-handle").attr("width",margin.left).attr("height",margin.left);
function updateSlider(index){
slider.noUiSlider.set(years[index]);
}
button.on("click",function(){
if(button.classed("reset")){
index = 0;
updateWidth(index);
updateButton("reset","play");
updateDisplayYear(index);
updateSlider(index);
}else if(button.classed("play")){
timer = d3.interval(function(elapsed){
index +=1;
if(index < years.length){
updateWidth(index);
updateButton("play","pause");
updateDisplayYear(index);
updateSlider(index);
} else{
index -= 1; //hard coding for tooltips to make sense
timer.stop();
updateButton("play","reset");
}},500);
}else if(button.classed("pause")){
timer.stop();
updateButton("pause","play");
}
})
slider.noUiSlider.on('slide', function(){
year = parseInt(slider.noUiSlider.get());
index = years.indexOf(year);
currClass = button.attr("class")
updateWidth(index);
updateDisplayYear(index);
if(year == d3.max(years)){
updateButton(currClass,"reset");
} else{
updateButton(currClass,"play")
};
})
});
</script>
</body>
</html>
https://d3js.org/d3.v4.min.js
https://d3js.org/d3-scale.v1.min.js
https://d3js.org/d3-drag.v1.min.js