D3
OG
Old school D3 from simpler times
All examples
By author
By category
About
reinson
Full window
Github gist
Demographics of Estonia
<!DOCTYPE html> <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