Side by side age range comparison between genders. Sortable by total or by genders.
The complete version with option to choose between different population groups can be found at my site here
xxxxxxxxxx
<head>
<meta charset="utf-8"></meta>
<title>
Age range of genders
</title>
<style>
body {
font: 10px sans-serif;
}
.axis path,
.axis line {
fill: none;
stroke: #000;
shape-rendering: crispEdges;
}
.y.axis path, .yMale.axis path, .yFemale.axis path{
display: none;
}
.peopleText{
font-size:12px;
font-weight:bold;
pointer-events:none;
}
</style>
<body>
<form>
<label><input class="formationRadio" type="radio" name="mode" value="all">All</label>
<label><input class="formationRadio" type="radio" name="mode" value="gender" checked>Gender</label>
<label><input id="sort" type="checkbox">Sort</input></label>
</form>
<div></div>
<script src="https://d3js.org/d3.v3.min.js"></script>
<script>
var margin = {top: 20, right: 100, bottom: 100, left: 100},
width = 960 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
function formatPeople(amt){return d3.format(",")(amt);}
var ageRanges = [];//["0-4", "5-9", "10-14", "15-19", "20-24", "25-29", "30-34", "35-39", "40-44", "45-49", "50-54", "55-59", "60-64", "65-69", "70-74", "75-79", "80+"];
var yMale = d3.scale.ordinal()
//.domain(ageRanges)
.rangeRoundBands([height, 0], .1);
var yFemale = d3.scale.ordinal()
//.domain(ageRanges)
.rangeRoundBands([height, 0], .1);
var xMale = d3.scale.linear()
.rangeRound([0, width/2]);
var xFemale = d3.scale.linear()
.rangeRound([width/2, width]);
var color = d3.scale.ordinal()
.range(["#98abc5", "#8a89a6"]);
var ageRangeColor = d3.scale.ordinal()
.range(["#ccffe6", "#b3ffda", "#99ffce", "#80ffc1", "#66ffb5", "#4dffa9", "#33ff9c", "#1aff90", "#00ff84", "#00e677", "#00cc69",
"#00b35c", "#00994f", "#008042", "#006635", "#004d28", "#00331a"]);
var genderColor = d3.scale.ordinal()
.range(["#0099ff", "#ff6699"]);
var genders = [];
var xAxisMale = d3.svg.axis()
.scale(xMale)
.orient("bottom")
.tickFormat(d3.format(".2s"));
var xAxisFemale = d3.svg.axis()
.scale(xFemale)
.orient("bottom")
.tickFormat(d3.format(".2s"));
var yAxisMale = d3.svg.axis()
.scale(yMale)
.orient("left");
var yAxisFemale = d3.svg.axis()
.scale(yFemale)
.orient("right");
//var data;
var svg = d3.select("div").append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom);
var canvas = svg.append("g")
.attr("class", "canvas")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
canvas.append("g")
.attr("class", "yMale axis")
.call(yAxisMale);
canvas.append("g")
.attr("class", "xMale axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxisMale);
canvas.append("g")
.attr("class", "yFemale axis")
.attr("transform", "translate(" + width + ",0)")
.call(yAxisFemale);
canvas.append("g")
.attr("class", "xFemale axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxisFemale);
var isAnimating = false;
var x0;
var activeLink = "none";
function render(file){
d3.csv(file, function(error, data) {
if (error) throw error;
var totalPeople = 0, totalMale = 0, totalFemale = 0;
data.forEach(function(d){
d.Total = 0;
d["Male"]=+d["Male"];
totalMale += d["Male"];
d["Female"]=+d["Female"];
totalFemale += d["Female"];
d.Total += d["Male"];
d.Total += d["Female"];
totalPeople += d.Total;
});
if(genders.length === 0){
genders = d3.keys(data[0]).filter(function(key){return key!="Age";});
genderColor.domain(genders);
}
var layers = d3.layout.stack()(genders.map(function(c) {
return data.map(function(d) {
//console.log(d);
return {x: d["Age"], y: +d[c], total: d.Total};
});
}));
console.log(layers);
var totalMax = d3.max(layers[0], function(d){return d.total;});//since total is the same for male and female
//just call d3.max once for a single layer....
var maleMax = d3.max(layers[0], function(d){return d.y;});
var maleSum = d3.sum(layers[0], function(d){return d.y;});
var femaleMax = d3.max(layers[1], function(d){return d.y;});
var femaleSum = d3.sum(layers[1], function(d){return d.y;});
var totalPopulation = maleSum + femaleSum;
var maxPopulation = Math.max(maleMax, femaleMax);
if(ageRanges.length === 0){
ageRanges = data.map(function(d){return d.Age;});
ageRangeColor.domain(ageRanges);
}
d3.select("input#sort").property("checked", false);
setupYAxises();
function setupYAxises(){
yMale.domain(ageRanges);
yFemale.domain(ageRanges);
canvas.select(".yMale.axis")
.transition().duration(1000)
.call(yAxisMale);
canvas.select(".yFemale.axis")
.transition().duration(1000)
.call(yAxisFemale);
}
var mode = d3.selectAll("input.formationRadio[name='mode']:checked").node().value;
var maleBars = canvas.selectAll(".maleBars")
.data(layers[0]);
maleBars.enter().append("rect")
.attr("class", "maleBars")
.style("fill", "#0099ff")
.attr("width", 0)
.attr("x", width/2);
var femaleBars = canvas.selectAll(".femaleBars")
.data(layers[1]);
femaleBars.enter().append("rect")
.attr("class", "femaleBars")
.style("fill", "#ff6699")
.attr("width", 0)
.attr("x", width/2);
function renderGenders(){
xMale.domain([maxPopulation, 0]).range([0, width/2]);//need to re-adjust the .range also...
xFemale.domain([0, maxPopulation]);
canvas.select(".xMale.axis")
.transition().duration(1000)
.call(xAxisMale);
canvas.select(".xFemale.axis")
.transition().duration(1000)
.call(xAxisFemale)
.style("opacity", 1);
canvas.select(".yFemale.axis")
.transition().duration(1000)
.style("opacity", 1);
/*maleBars
.attr("width", 0)
.attr("x", width/2); */
maleBars
.on("mouseover", function(d){
//console.log(d);
d3.select(this).style("stroke", "black").style("stroke-width", 1.5);
canvas.select(".yMale.axis").selectAll("text")
.filter(function(dText){return dText === d.x;})
.attr("font-size", 14)
.style("font-weight", "bold");
var tipX = d3.mouse(this)[0];
canvas.append("text")
.attr("class", "peopleText")
.text(formatPeople(d.y) + " males.")
.attr("x", ((tipX - 90) < 10?10:tipX - 90))
.attr("y", yMale(d.x) +15);
})
.on("mousemove", function(d){
var tipX = d3.mouse(this)[0];
canvas.select(".peopleText")
.attr("x", ((tipX - 90) < 10?10:tipX - 90))
.attr("y", yMale(d.x) + 15);
})
.on("mouseout", function(d){
d3.select(this).style("stroke", "none").style("stroke-width", 0);
canvas.select(".yMale.axis").selectAll("text")
.filter(function(dText){return dText === d.x;})
.attr("font-size", 10)
.style("font-weight", "normal");
canvas.select(".peopleText").remove();
});
maleBars
.transition().duration(1000)
.attr("y", function(d){return yMale(d.x);})
.attr("height", yMale.rangeBand())
.attr("x", function(d){return xMale(d.y);})
.attr("width", function(d){return width/2 - xMale(d.y);})
//.transition().duration(250)
.style("fill", "#0099ff");
femaleBars
.on("mouseover", function(d){
d3.select(this).style("stroke", "black").style("stroke-width", 1.5);
canvas.select(".yFemale.axis").selectAll("text")
.filter(function(dText){return dText === d.x;})
.attr("font-size", 14)
.style("font-weight", "bold");
var tipX = d3.mouse(this)[0];
canvas.append("text")
.attr("class", "peopleText")
.text(formatPeople(d.y) + " females.")
.attr("x", ((tipX + 10) > width - 100?width - 100:tipX + 10))
.attr("y", yFemale(d.x) +15);
})
.on("mousemove", function(d){
var tipX = d3.mouse(this)[0];
canvas.select(".peopleText")
.attr("x", ((tipX + 10) > width - 100?width - 100:tipX + 10))
.attr("y", yFemale(d.x) + 15);
})
.on("mouseout", function(d){
d3.select(this).attr("stroke", "none").style("stroke-width", 0);
canvas.select(".yFemale.axis").selectAll("text")
.filter(function(dText){return dText === d.x;})
.attr("font-size", 10)
.style("font-weight", "normal");
canvas.select(".peopleText").remove();
});
femaleBars
.transition().duration(1000)
.attr("y", function(d){return yFemale(d.x);})
.attr("height", yFemale.rangeBand())
.attr("x", width/2)
.attr("width", function(d){return xFemale(d.y) - width/2;})
.style("fill", "#ff6699");
}
function renderTotal(){
canvas.select(".xFemale.axis")
.transition().duration(1000)
.style("opacity", 0);
canvas.select(".yFemale.axis")
.transition().duration(1000)
.style("opacity", 0);
xMale.domain([0, totalMax]).rangeRound([0, width]);
canvas.select(".xMale.axis")
.transition().duration(1000)
.call(xAxisMale);
maleBars
.transition().duration(1000).delay(isNew?0:1000)
.attr("y", function(d){return yMale(d.x);})
.attr("height", yMale.rangeBand())
.attr("x", function(d) { return xMale(d.y0); })
.attr("width", function(d){return xMale(d.y0 + d.y) - xMale(d.y0);})
.style("fill", function(d){return ageRangeColor(d.x);});
maleBars
.on("mouseover", function(d){
canvas.selectAll("rect")
.filter(function(dRect){return dRect.x === d.x;})
.style("fill", "#ffff33");
canvas.select(".yMale.axis").selectAll("text")
.filter(function(dText){return dText === d.x;})
.attr("font-size", 14)
.style("font-weight", "bold");
mouseOverTip(d, d3.mouse(this)[0]);
})
.on("mousemove", function(d){
mouseMoveTip(d3.mouse(this)[0]);
})
.on("mouseout", function(d){
canvas.selectAll("rect")
.filter(function(dRect){return dRect.x === d.x;})
.style("fill", function(dRect){return ageRangeColor(dRect.x);});
canvas.select(".yMale.axis").selectAll("text")
.filter(function(dText){return dText === d.x;})
.attr("font-size", 10)
.style("font-weight", "normal");
canvas.select(".peopleText").remove();
});
femaleBars
.transition().duration(1000).delay(isNew?0:1500)
.attr("y", function(d){return yMale(d.x);})
.attr("height", yMale.rangeBand())
.attr("x", function(d) { return xMale(d.y0); })
.attr("width", function(d){return xMale(d.y0 + d.y) - xMale(d.y0);})
//.transition().duration(250)
.style("fill", function(d){return ageRangeColor(d.x);});
femaleBars
.on("mouseover", function(d){
canvas.selectAll("rect")
.filter(function(dRect){return dRect.x === d.x;})
.style("fill", "#ffff33");
canvas.select(".yMale.axis").selectAll("text")
.filter(function(dText){return dText === d.x;})
.attr("font-size", 14)
.style("font-weight", "bold");
mouseOverTip(d, d3.mouse(this)[0]);
})
.on("mousemove", function(d){
mouseMoveTip(d3.mouse(this)[0]);
})
.on("mouseout", function(d){
canvas.selectAll("rect")
.filter(function(dRect){return dRect.x === d.x;})
.style("fill", function(dRect){return ageRangeColor(dRect.x);});
canvas.select(".yMale.axis").selectAll("text")
.filter(function(dText){return dText === d.x;})
.attr("font-size", 10)
.style("font-weight", "normal");
canvas.select(".peopleText").remove();
});
function mouseOverTip(d, tipX){
canvas.append("text")
.attr("class", "peopleText")
.text(formatPeople(d.total) + " people.")
.attr("x", tipX + 10)
.attr("y", yMale(d.x) +15);
}
function mouseMoveTip(tipX){
canvas.select(".peopleText")
.attr("x", tipX + 10);
}
}
maleBars.exit().remove();
femaleBars.exit().remove();
if(d3.selectAll("input.formationRadio[name='mode']:checked").node().value === "gender")renderGenders();
else renderTotal();
isNew = false;
d3.selectAll("input.formationRadio").on("change", changeFormation);
function changeFormation() {
d3.select("input#sort").property("checked", false);
setupYAxises();
if (this.value === "gender") renderGenders();
else renderTotal();
}
d3.select("input#sort").on("change", change);
function change(){
var transition = svg.transition().duration(750),
delay = function(d, i) { return i * 50; };
if(d3.selectAll("input.formationRadio[name='mode']:checked").node().value === "gender"){
var y0Male = yMale.domain(layers[0].sort(this.checked
? function(a, b) { return b.y - a.y; }
: function(a, b) { return ageRanges.indexOf(a.x) - ageRanges.indexOf(b.x); })
.map(function(d) { return d.x; }))
.copy();
canvas.selectAll(".maleBars")
.sort(function(a, b) { return y0Male(a.y) - y0Male(b.y); });
transition.selectAll(".maleBars")
.delay(delay)
.attr("y", function(d) { return y0Male(d.x); });
transition.select(".canvas").select(".yMale.axis")
.call(yAxisMale)
.selectAll("g")
.delay(delay);
var y0Female = yFemale.domain(layers[1].sort(this.checked
? function(a, b) { return b.y - a.y; }
: function(a, b) { console.log(a.x);return ageRanges.indexOf(a.x) - ageRanges.indexOf(b.x); })
.map(function(d) { return d.x; }))
.copy();
canvas.selectAll(".femaleBars")
.sort(function(a, b) { return y0Female(a.y) - y0Female(b.y); });
transition.selectAll(".femaleBars")
.delay(delay)
.attr("y", function(d) { return y0Female(d.x); });
transition.select(".canvas").select(".yFemale.axis")
.call(yAxisFemale)
.selectAll("g")
.delay(delay);
}
else{
var y0Male = yMale.domain(layers[0].sort(this.checked
? function(a, b) { return b.total - a.total; }
: function(a, b) { return ageRanges.indexOf(a.x) - ageRanges.indexOf(b.x); })
.map(function(d) { return d.x; }))
.copy();
canvas.selectAll(".maleBars")
.sort(function(a, b) { return y0Male(a.y) - y0Male(b.y); });
transition.selectAll(".maleBars")
.delay(delay)
.attr("y", function(d) { return y0Male(d.x); });
transition.select(".canvas").select(".yMale.axis")
.call(yAxisMale)
.selectAll("g")
.delay(delay);
canvas.selectAll(".femaleBars")
.sort(function(a, b) { return y0Male(a.y) - y0Male(b.y); });
transition.selectAll(".femaleBars")
.delay(delay)
.attr("y", function(d) { return y0Male(d.x); });
}
}
});
}
//..initial render condition...
render("people.csv");
</script>
</body>
</head>
Modified http://d3js.org/d3.v3.min.js to a secure url
https://d3js.org/d3.v3.min.js