xxxxxxxxxx
<head>
<style>
.x.axis path.domain {
display: none
}
.hidden {
display: none
}
.sparkWrapper text{
text-anchor: middle;
}
text {
font-family: "Lucida Grande", "Lucida Sans Unicode", "Lucida Sans", Geneva, Verdana, sans-serif;
fill: #696969;
}
.bornIn {
font-size: 25px;
font-style: normal;
font-variant: normal;
font-weight: 500;
line-height: 15.4px;
}
select {
position: absolute;
left: 475px;
top: 26px;
}
.arrow {
stroke: #000;
stroke-width: 1px;
}
</style>
<meta charset="utf-8">
<script src="//d3js.org/d3.v4.min.js"></script>
</head>
<select name="country-list">
<option value="wiggle">Minimized wiggle</option>
<option value="none">Zero baseline</option>
<option value="expand">Normalized values</option>
</select>
<script>
var margin = {top: 60, right: 200, bottom: 120, left: 65, between: 50};
var width = 800 - margin.right - margin.left,
height = 480 - margin.top - margin.bottom;
var x = d3.scaleLinear()
.domain([1923,2040])
.range([0, width]);
var y = d3.scaleLinear()
.range([height, 0]);
var area = d3.area()
.x(function(d,i) { return x(d.data.Aasta); })
.y0(function(d) { return y(d[0]); })
.y1(function(d) { return y(d[1]); });
var stack = d3.stack()
.order(d3.stackOrderNone)
.offset(d3.stackOffsetWiggle );
var xAxis = d3.axisBottom()
.tickFormat(d3.format(""))
.scale(x);
var yAxis = d3.axisRight()
.scale(y);
var color = d3.interpolateRainbow;
var sparkYLocal = d3.local();
var sparkX = d3.scaleLinear().domain([1923,2040]).range([0,100]);
var sparkLine;
function draw(error,data) {
//////////////////////////
// (1) Transform data ////
//////////////////////////
var nested = d3.nest()
.key(function (d) {
return d.Sugu
})
.entries(data);
var ageGroups = d3.keys(data[0]).filter(function (d) {
return ["Sugu", "Kokku", "Aasta"].indexOf(d) === -1;
});
data = stack.keys(ageGroups)(nested[0].values);
var y_min = d3.min(data[0], function (d) {
return d[0]
});
var y_max = d3.max(data[data.length - 1], function (d) {
return d[1]
});
y.domain([y_min, y_max]);
/////////////////////
// (2) Create svg //
///////////////////
var svg = d3.select("body").append("svg")
.attr("width", width + margin.right + margin.left)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
////////////////////////////////////////////////////
// (3) Create linear gradients for colored paths ///
///////////////////////////////////////////////////
var defs = svg.append("defs");
var xLength = (x.domain()[1] - x.domain()[0]),
offsetStart = -(85 / xLength)*100, // color scale must start 85 years before beginning of x
offsetStep = 4,
shiftPerLine = 100 / xLength * 5;
var percentStops = d3.range(offsetStart, 101, offsetStep);
var gradientOffset = data.map(function (_, i) {
return percentStops.map(function (d) {
return {
p: d + i * shiftPerLine,
color: color((d-offsetStart)/(100-offsetStart))
}
});
});
defs.selectAll("linearGradient").data(gradientOffset)
.enter()
.append("linearGradient")
.attr("id", function (_, i) {
return "lg" + i
})
.attr("x1", "0%").attr("y1", "0%")
.attr("x2", "100%").attr("y2", "0%")
.each(function (grad_data) {
var linearGradient = d3.select(this);
linearGradient.selectAll("stop").data(grad_data)
.enter().append("stop")
.attr("offset", function (d) {
return d.p + "%";
})
.attr("stop-color", function (d) {
return d.color;
});
});
//////////////////////
// (4) Create pahts //
/////////////////////
svg.append("g").attr("class","pathWrapper")
.selectAll("path")
.data(data)
.enter().append("path")
.attr("class","areas")
.attr("d", area)
.style("fill", function (_, i) {
return "url(#lg" + i + " )"
})
.attr("stroke","white").attr("stroke-width","0.5px")
.on("mouseover", mouseOver)
.on("mousemove", mouseMove)
.on("mouseout", mouseOut);
//////////////////////////////////
// (5) Create labels and guides //
/////////////////////////////////
var ageRangeLabel = svg.append("text").attr("x", -5).attr("class", "hidden")
.attr("text-anchor","end");
svg.append("rect") // Missing data box
.attr("x", x(1939)).attr("y", 0)
.attr("width", x(1950) - x(1939)).attr("height", "300")
.attr("opacity", 0.5).attr("fill", "white")
.on("mouseover", function () {
yearLabel.text("missing data").classed("hidden", false);
})
.on("mousemove",function(){
yearLabel.attr("x",d3.event.x-margin.left-30).attr("y",d3.event.y-margin.top-20);
})
.on("mouseout",function(){
yearLabel.classed("hidden",true)
})
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + (height + 5) + ")")
.call(xAxis);
var yearLabel = svg.append("text").attr("class", "hidden").attr("text-anchor","middle").text("tere"),
yearLine = svg.append("line").attr("x1", 100).attr("x2", 100).attr("y1", 20).attr("y2", 400)
.attr("stroke-width", "2px").attr("stroke", "white").attr("class", "hidden")
.attr("opacity", 0.6).style("pointer-events", "none");
svg.append("text").text("Demographics of Estonia").style("font-size", "25px") // Title
.attr("x", 0).attr("y", -20)
.style("text-anchor", "start");
svg.append("text").text("Hover areas for details").attr("y",height-10);
//////////////////////////////////////
// (6) Create birth years legend box //
/////////////////////////////////////
var legendBox = svg.append("g").attr("transform", "translate(0," + (height + 60) + ")");
defs.append("linearGradient")
.attr("id", "legend_lg")
.attr("x1", "0%")
.attr("y1", "0%")
.attr("x2", "100%")
.attr("y2", "0%")
.selectAll("stop")
.data(d3.range(0, 101, 4).map(function (d) {
return {
p: d + "%",
color: color(d / 100)
}
})
).enter().append("stop")
.attr("offset", function (d) {
return d.p
})
.attr("stop-color", function (d) {
return d.color
});
legendBox.append("rect").style("fill", "url(#legend_lg)")
.attr("width", width/6*4)
.attr("height", 15)
.attr("x", width/6)
.attr("y", 10);
var boxAxis = d3.axisTop()
.ticks(7)
.tickFormat(d3.format(""))
.scale(x.copy()
.range([width/6, width/6*5])
.domain([1840 , 2040]));
legendBox.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0,10)")
.call(boxAxis);
legendBox.append("text").text("Birth years").attr("x", width / 2).attr("y",52)
.style("text-anchor", "middle")
.classed("bornIn", true);
//////////////////////////////////////////////
// (7) Compute sparkline data and draw them //
//////////////////////////////////////////////
var populationSize = nested[0].values.map(function(d){
return {
x: d.Aasta,
y: d.Kokku
}});
var averageAgeData = nested[0].values.map(function(d){
var values = filterAgeGroups(d);
var numerator = d3.sum(values, function(d){
return d3.mean(d.key.split("-")
.map(function(d){return d=="85+" ? 88 : +d})
) * d.value;
});
var denominator = d3.sum(values, function(d){return d.value});
return {
x: d.Aasta,
y: numerator/denominator
}
});
var retiredToWorkers = nested[0].values.map(function(d){
var values = filterAgeGroups(d);
var retiredSum = d3.sum(values, function(d){
return +d.key.split("-")[0] >= 65 ? d.value : d.key =="85+" ? d.value : 0;
});
var workersSum = d3.sum(values, function(d){
var rangeStart = +d.key.split("-")[0];
if (rangeStart < 65 && rangeStart >= 20){
return d.value
}
return 0;
});
return {x: d.Aasta,
y: workersSum / retiredSum
}
});
var ageGroupDynamics = {};
ageGroups.forEach(function(ageGroup){
ageGroupDynamics[ageGroup] = nested[0].values.map(function(d){
return {
x: d.Aasta,
y: d[ageGroup] / d3.sum(filterAgeGroups(d), function(x){ return x.value})
}
})
});
var sparksData = [
{
values: populationSize,
name: "Population size",
id: "populationSize", format: ",d"
},
{
values: ageGroupDynamics["0-4"],
name: "0-4",
id: "customSpark", format: ",.1%"
},
{
values: averageAgeData,
name: "Average age",
id: "averageAge", format: ",d"
},
{
values: retiredToWorkers,
name: "20-64 to 65+ ratio",
id: "retiredToWorkers", format: ".2n", valueEnding: " : 1"
}
];
draw_sparklines(sparksData);
updateSparks(2016);
//////////////////////////////////
// (8) Mouse over functionality //
//////////////////////////////////
function mouseOver(d) {
d3.select(this).attr("opacity", 0.5);
ageRangeLabel.text(d.key)
.attr("y", function () {
return y((d[0][1] + d[0][0]) / 2) + 4
})
.classed("hidden", false);
var customSpark = d3.select("#customSpark");
customSpark.each(function(){
var customY = sparkYLocal.get(this);
customY.domain(d3.extent(ageGroupDynamics[d.key],function(d){return d.y}));
sparkLine.y(function(d){return customY(d.y)});
});
customSpark.datum({name: d.key, values:ageGroupDynamics[d.key],format: ",.1%"});
customSpark.select(".sparkLabel").text(d.key);
customSpark.select("path")
.attr("d",sparkLine(ageGroupDynamics[d.key]));
}
function mouseMove(d) {
var mouse = d3.mouse(this),
mouseYear = Math.floor(x.invert(mouse[0])),
yearData = nested[0].values.filter(function (d) {
return d.Aasta == mouseYear;
})[0],
stackRange = stackLineRange(data,mouseYear);
updateSparks(mouseYear);
yearLabel.text(mouseYear)
.attr("x", x(mouseYear))
.attr("y", y(stackRange.max) - 5)
.classed("hidden", false);
yearLine.attr("x1", x(mouseYear)).attr("x2", x(mouseYear))
.attr("y1", y(stackRange.max)).attr("y2", y(stackRange.min))
.classed("hidden", false);
}
function mouseOut(d) {
d3.select(this).attr("opacity", 1);
ageRangeLabel.classed("hidden", true);
yearLine.classed("hidden", true);
yearLabel.classed("hidden", true);
// yearPopulationLabel.classed("hidden", true)
}
////////////////////////////
// (9) Spark line drawing //
////////////////////////////
function draw_sparklines(data){
var xExtent = d3.extent(data[0].values, function(d){return d.x}),
sparkY = d3.scaleLinear().range([20,0]);
sparkLine = d3.line()
.curve(d3.curveCardinal)
.x(function(d){return sparkX(d.x)})
.y(function(d){return sparkY(d.y)});
svg.selectAll(".spark").data(data).enter().append("g").attr("class","sparkWrapper")
.attr("id",function(d){return d.id})
.attr("transform",function(d,i){return "translate("+ (width + margin.between) +"," + (i*100 + 25) + ")" })
.each(function(sparkData,i){
var sparkG = d3.select(this);
sparkG.append("text").text(sparkData.name).attr("class","sparkLabel")
.attr("transform","translate(50,-12)");
sparkG.append("text").attr("class","sparkValue").attr("transform","translate(50,42)");
var yExtent = d3.extent(sparkData.values, function(d){return d.y});
sparkY.domain(yExtent);
sparkYLocal.set(this,sparkY.copy());
sparkG.selectAll("path").data([sparkData.values]).enter().append("path")
.attr("d",sparkLine).style("fill","none").attr("stroke","black");
sparkG.append("circle").attr("r",2).attr("fill","red").attr("class","sparkDot");
});
}
function updateSparks(year){
svg.selectAll(".sparkWrapper").each(function(d){
var sparkG = d3.select(this),
activeValue = d.values.filter(function(d){return d.x==year})[0].y;
sparkG.select(".sparkValue")
.text(function(){
var value = d3.format(d.format)(activeValue);
return d.valueEnding ? value + d.valueEnding : value;
})
d3.select(this).select("circle")
.attr("cx",sparkX(year))
.attr("cy",sparkYLocal.get(this)(activeValue));
})
}
var menu = d3.select("select").on("change",change);
var menuDict = {"none": d3.stackOffsetNone, "expand":d3.stackOffsetExpand,
"silhouette": d3.stackOffsetSilhouette, "wiggle": d3.stackOffsetWiggle};
function change(d){
var activeValue = menu.property("value");
var newOffset = menuDict[activeValue];
stack.offset(newOffset);
data = stack.keys(ageGroups)(nested[0].values);
debugger;
var y_min = d3.min(data[0], function (d) {
return d[0]
});
var y_max = d3.max(data[data.length - 1], function (d) {
return d[1]
});
y.domain([y_min, y_max]);
svg.selectAll(".areas").data(data)
.transition().duration(750)
.attr("d",area);
}
///////////////////////////
// Some helper functions //
///////////////////////////
function stackLineRange(data,year){
var stackMin = data[0].filter(function (d) { // for finding bottom of the yearLine
return d.data.Aasta == year;
})[0][0],
stackMax = data[data.length - 1].filter(function (d) { // for finding the top of the yearLine
return d.data.Aasta == year;
})[0][1];
return {min: stackMin, max: stackMax}
}
function filterAgeGroups(yearData){
return d3.entries(yearData).filter(function(d){return ageGroups.indexOf(d.key)!= -1})
}
} // draw
function type(row) {
d3.keys(row).forEach(function(d){
if (d != "Sugu"){
row[d] = +row[d];
}
});
return row;
}
d3.csv("RV021.csv")
.row(type)
.get(draw);
</script>
https://d3js.org/d3.v4.min.js