Built with blockbuilder.org
forked from michalmatwie's block: stacked/grouped
| license: mit |
Built with blockbuilder.org
forked from michalmatwie's block: stacked/grouped
| State | Under 5 Years | 5 to 13 Years | 14 to 17 Years | 18 to 24 Years | 25 to 44 Years | 45 to 64 Years | 65 Years and Over | |
|---|---|---|---|---|---|---|---|---|
| AL | 310504 | 552339 | 259034 | 450818 | 1231572 | 1215966 | 641667 | |
| AK | 52083 | 85640 | 42153 | 74257 | 198724 | 183159 | 50277 | |
| AZ | 515910 | 828669 | 362642 | 601943 | 1804762 | 1523681 | 862573 | |
| AR | 202070 | 343207 | 157204 | 264160 | 754420 | 727124 | 407205 | |
| CA | 2704659 | 4499890 | 2159981 | 3853788 | 10604510 | 8819342 | 4114496 | |
| CO | 358280 | 587154 | 261701 | 466194 | 1464939 | 1290094 | 511094 | |
| CT | 211637 | 403658 | 196918 | 325110 | 916955 | 968967 | 478007 | |
| DE | 59319 | 99496 | 47414 | 84464 | 230183 | 230528 | 121688 | |
| DC | 36352 | 50439 | 25225 | 75569 | 193557 | 140043 | 70648 | |
| FL | 1140516 | 1938695 | 925060 | 1607297 | 4782119 | 4746856 | 3187797 | |
| GA | 740521 | 1250460 | 557860 | 919876 | 2846985 | 2389018 | 981024 | |
| HI | 87207 | 134025 | 64011 | 124834 | 356237 | 331817 | 190067 | |
| ID | 121746 | 201192 | 89702 | 147606 | 406247 | 375173 | 182150 | |
| IL | 894368 | 1558919 | 725973 | 1311479 | 3596343 | 3239173 | 1575308 | |
| IN | 443089 | 780199 | 361393 | 605863 | 1724528 | 1647881 | 813839 | |
| IA | 201321 | 345409 | 165883 | 306398 | 750505 | 788485 | 444554 | |
| KS | 202529 | 342134 | 155822 | 293114 | 728166 | 713663 | 366706 | |
| KY | 284601 | 493536 | 229927 | 381394 | 1179637 | 1134283 | 565867 | |
| LA | 310716 | 542341 | 254916 | 471275 | 1162463 | 1128771 | 540314 | |
| ME | 71459 | 133656 | 69752 | 112682 | 331809 | 397911 | 199187 | |
| MD | 371787 | 651923 | 316873 | 543470 | 1556225 | 1513754 | 679565 | |
| MA | 383568 | 701752 | 341713 | 665879 | 1782449 | 1751508 | 871098 | |
| MI | 625526 | 1179503 | 585169 | 974480 | 2628322 | 2706100 | 1304322 | |
| MN | 358471 | 606802 | 289371 | 507289 | 1416063 | 1391878 | 650519 | |
| MS | 220813 | 371502 | 174405 | 305964 | 764203 | 730133 | 371598 | |
| MO | 399450 | 690476 | 331543 | 560463 | 1569626 | 1554812 | 805235 | |
| MT | 61114 | 106088 | 53156 | 95232 | 236297 | 278241 | 137312 | |
| NE | 132092 | 215265 | 99638 | 186657 | 457177 | 451756 | 240847 | |
| NV | 199175 | 325650 | 142976 | 212379 | 769913 | 653357 | 296717 | |
| NH | 75297 | 144235 | 73826 | 119114 | 345109 | 388250 | 169978 | |
| NJ | 557421 | 1011656 | 478505 | 769321 | 2379649 | 2335168 | 1150941 | |
| NM | 148323 | 241326 | 112801 | 203097 | 517154 | 501604 | 260051 | |
| NY | 1208495 | 2141490 | 1058031 | 1999120 | 5355235 | 5120254 | 2607672 | |
| NC | 652823 | 1097890 | 492964 | 883397 | 2575603 | 2380685 | 1139052 | |
| ND | 41896 | 67358 | 33794 | 82629 | 154913 | 166615 | 94276 | |
| OH | 743750 | 1340492 | 646135 | 1081734 | 3019147 | 3083815 | 1570837 | |
| OK | 266547 | 438926 | 200562 | 369916 | 957085 | 918688 | 490637 | |
| OR | 243483 | 424167 | 199925 | 338162 | 1044056 | 1036269 | 503998 | |
| PA | 737462 | 1345341 | 679201 | 1203944 | 3157759 | 3414001 | 1910571 | |
| RI | 60934 | 111408 | 56198 | 114502 | 277779 | 282321 | 147646 | |
| SC | 303024 | 517803 | 245400 | 438147 | 1193112 | 1186019 | 596295 | |
| SD | 58566 | 94438 | 45305 | 82869 | 196738 | 210178 | 116100 | |
| TN | 416334 | 725948 | 336312 | 550612 | 1719433 | 1646623 | 819626 | |
| TX | 2027307 | 3277946 | 1420518 | 2454721 | 7017731 | 5656528 | 2472223 | |
| UT | 268916 | 413034 | 167685 | 329585 | 772024 | 538978 | 246202 | |
| VT | 32635 | 62538 | 33757 | 61679 | 155419 | 188593 | 86649 | |
| VA | 522672 | 887525 | 413004 | 768475 | 2203286 | 2033550 | 940577 | |
| WA | 433119 | 750274 | 357782 | 610378 | 1850983 | 1762811 | 783877 | |
| WV | 105435 | 189649 | 91074 | 157989 | 470749 | 514505 | 285067 | |
| WI | 362277 | 640286 | 311849 | 553914 | 1487457 | 1522038 | 750146 | |
| WY | 38253 | 60890 | 29314 | 53980 | 137338 | 147279 | 65614 |
| <!DOCTYPE html> | |
| <meta charset="utf-8"> | |
| <style> | |
| body { | |
| font: 10px sans-serif; | |
| } | |
| .axis path, | |
| .axis line { | |
| fill: none; | |
| stroke: #000; | |
| shape-rendering: crispEdges; | |
| } | |
| .bar { | |
| fill: steelblue; | |
| } | |
| .x.axis path { | |
| display: none; | |
| } | |
| .tooltip{ | |
| text-anchor: middle; | |
| font-family: sans-serif; | |
| font-size: 10px; | |
| font-weight: bold; | |
| fill:black; | |
| } | |
| </style> | |
| <body> | |
| <form> | |
| <label><input type="radio" name="mode" value="grouped"> Grouped</label> | |
| <label><input type="radio" name="mode" value="stacked" checked> Stacked</label> | |
| </form> | |
| <script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.13/d3.min.js"></script> | |
| <script> | |
| //SVG canvas size settings | |
| const margin = {top: 20, right: 20, bottom: 30, left: 40}, | |
| width = 963 - margin.left - margin.right, | |
| height = 428 - margin.top - margin.bottom; | |
| //Scales & Axis | |
| const x = d3.scale.ordinal() | |
| .rangeRoundBands([0, width], .1); | |
| const y = d3.scale.linear() | |
| .rangeRound([height, 0]); | |
| const yGrouped = d3.scale.linear().rangeRound([10604510 ,0]); | |
| const color = d3.scale.category20c(); | |
| const xAxis = d3.svg.axis() | |
| .scale(x) | |
| .orient("bottom"); | |
| const yAxis = d3.svg.axis() | |
| .scale(y) | |
| .orient("left") | |
| .tickFormat(d3.format(".2s")); | |
| const yAxisGrouped = d3.svg.axis().scale(yGrouped).orient("left").tickFormat(d3.format(".2s")); | |
| //SVG canvas | |
| const 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 + ")"); | |
| let active_link = "0"; //to control legend selections and hover | |
| let legendClicked; //to control legend selections | |
| let legendClassArray = []; //store legend classes to select bars in plotSingle() | |
| let y_orig; //to store original y-posn | |
| d3.csv("age_data.csv", function(error, data) { | |
| if (error) throw error; | |
| color.domain(d3.keys(data[0]).filter(function(key) { return key !== "State"; })); | |
| data.forEach(function(d) { | |
| let mystate = d.State; //add to stock code | |
| let y0 = 0; | |
| //d.ages = color.domain().map(function(name) { return {name: name, y0: y0, y1: y0 += +d[name]}; }); | |
| d.ages = color.domain().map(function(name) { return {mystate:mystate, name: name, y0: y0, y1: y0 += +d[name]}; }); | |
| d.total = d.ages[d.ages.length - 1].y1; | |
| }); | |
| data.sort(function(a, b) { return b.total - a.total; }); | |
| x.domain(data.map(function(d) { return d.State; })); | |
| y.domain([0, d3.max(data, function(d) { return d.total; })]); | |
| svg.append("g") | |
| .attr("class", "x axis") | |
| .attr("transform", "translate(0," + height + ")") | |
| .call(xAxis); | |
| svg.append("g") | |
| .attr("class", "y axis") | |
| .call(yAxis) | |
| .append("text") | |
| .attr("transform", "rotate(-90)") | |
| .attr("y", 6) | |
| .attr("dy", ".71em") | |
| .style("text-anchor", "end"); | |
| //.text("Population"); | |
| const state = svg.selectAll(".state") | |
| .data(data) | |
| .enter().append("g") | |
| .attr("class", "g") | |
| .attr("transform", function(d) { return "translate(" + "0" + ",0)"; }); | |
| //.attr("transform", function(d) { return "translate(" + x(d.State) + ",0)"; }) | |
| state.selectAll("rect") | |
| .data(function(d) { | |
| return d.ages; | |
| }) | |
| .enter().append("rect") | |
| .attr("width", x.rangeBand()) | |
| .attr("y", function(d) { return y(d.y1); }) | |
| .attr("x",function(d) { //add to stock code | |
| return x(d.mystate) | |
| }) | |
| .attr("height", function(d) { return y(d.y0) - y(d.y1); }) | |
| .attr("class", function(d) { | |
| classLabel = d.name.replace(/\s/g, ''); //remove spaces | |
| return "class" + classLabel; | |
| }) | |
| .style("fill", function(d) { return color(d.name); }); | |
| // state.selectAll('rect') | |
| // .data(d => d.ages) | |
| // .enter().append('rect') | |
| // .attr('width', x.rangeBand() / 7) | |
| // .attr('y', d => { | |
| // return y(d.y1 - d.y0) | |
| // }) | |
| // .attr('x', (d, i) => x(d.mystate) + i * (x.rangeBand() / 7)) | |
| // .attr('height', d => y(d.y0) - y(d.y1)) | |
| // .attr("class", function(d) { | |
| // classLabel = d.name.replace(/\s/g, ''); //remove spaces | |
| // return "class" + classLabel; | |
| // }) | |
| // .style("fill", function(d) { return color(d.name); }); | |
| function transitionGrouped() { | |
| state.selectAll('rect').data(d => d.ages) | |
| .transition() | |
| .duration(500) | |
| .attr('x', (d, i) => x(d.mystate) + i * (x.rangeBand() / 7)) | |
| .attr('width', x.rangeBand() / 7) | |
| .attr('y', d => y(d.y1 - d.y0)) | |
| } | |
| function transitionStacked() { | |
| state.selectAll('rect').data(d => d.ages) | |
| .transition() | |
| .duration(500) | |
| .attr('x', d => x(d.mystate)) | |
| .attr('width', x.rangeBand()) | |
| .attr('y', d => y(d.y1)) | |
| } | |
| d3.selectAll("input") | |
| .on("change", changed); | |
| function changed() { | |
| if (this.value === "grouped") transitionGrouped(); | |
| else transitionStacked(); | |
| } | |
| state.selectAll("rect") | |
| .on("mouseover", function(d){ | |
| let delta = d.y1 - d.y0; | |
| let xPos = parseFloat(d3.select(this).attr("x")); | |
| let yPos = parseFloat(d3.select(this).attr("y")); | |
| let height = parseFloat(d3.select(this).attr("height")) | |
| d3.select(this).attr("stroke","blue").attr("stroke-width",0.8); | |
| let rect = svg.append("g") | |
| .attr('transform', `translate(${xPos}, ${yPos + height / 2})`) | |
| .attr("class","tooltip") | |
| .append('rect') | |
| let tooltip = svg.select('.tooltip').append('text') | |
| .text(d.name +": "+ delta); | |
| let BBox = tooltip.node().getBBox(); | |
| rect.attr('width', BBox.width) | |
| .attr('height', BBox.height) | |
| .attr("x", BBox.x) | |
| .attr("y", BBox.y) | |
| .attr('fill', 'white') | |
| .attr('rx', 5) | |
| }) | |
| .on("mouseout",function(){ | |
| svg.select(".tooltip").remove(); | |
| d3.select(this).attr("stroke","pink").attr("stroke-width",0.2); }) | |
| const legend = svg.selectAll(".legend") | |
| .data(color.domain().slice().reverse()) | |
| .enter().append("g") | |
| //.attr("class", "legend") | |
| .attr("class", function (d) { | |
| legendClassArray.push(d.replace(/\s/g, '')); //remove spaces | |
| return "legend"; | |
| }) | |
| .attr("transform", function(d, i) { return "translate(0," + i * 20 + ")"; }); | |
| //reverse order to match order in which bars are stacked | |
| legendClassArray = legendClassArray.reverse(); | |
| legend.append("rect") | |
| .attr("x", width - 18) | |
| .attr("width", 18) | |
| .attr("height", 18) | |
| .style("fill", color) | |
| .attr("id", function (d, i) { | |
| return "id" + d.replace(/\s/g, ''); | |
| }) | |
| .on("mouseover",function(){ | |
| if (active_link === "0") d3.select(this).style("cursor", "pointer"); | |
| else { | |
| if (active_link.split("class").pop() === this.id.split("id").pop()) { | |
| d3.select(this).style("cursor", "pointer"); | |
| } else d3.select(this).style("cursor", "auto"); | |
| } | |
| }) | |
| .on("click",function(d){ | |
| if (active_link === "0") { //nothing selected, turn on this selection | |
| d3.select(this) | |
| .style("stroke", "black") | |
| .style("stroke-width", 2); | |
| active_link = this.id.split("id").pop(); | |
| plotSingle(this); | |
| //gray out the others | |
| for (i = 0; i < legendClassArray.length; i++) { | |
| if (legendClassArray[i] != active_link) { | |
| d3.select("#id" + legendClassArray[i]) | |
| .style("opacity", 0.5); | |
| } | |
| } | |
| } else { //deactivate | |
| if (active_link === this.id.split("id").pop()) {//active square selected; turn it OFF | |
| d3.select(this) | |
| .style("stroke", "none"); | |
| active_link = "0"; //reset | |
| //restore remaining boxes to normal opacity | |
| for (i = 0; i < legendClassArray.length; i++) { | |
| d3.select("#id" + legendClassArray[i]) | |
| .style("opacity", 1); | |
| } | |
| //restore plot to original | |
| restorePlot(d); | |
| } | |
| } //end active_link check | |
| }); | |
| legend.append("text") | |
| .attr("x", width - 24) | |
| .attr("y", 9) | |
| .attr("dy", ".35em") | |
| .style("text-anchor", "end") | |
| .text(function(d) { return d; }); | |
| state.selectAll("rect") | |
| .on("click",function(d){ | |
| let range = this.getAttribute('class').split("class").pop(); | |
| if (active_link === "0") { //nothing selected, turn on this selection | |
| d3.select(`#id${range}`) | |
| .style("stroke", "black") | |
| .style("stroke-width", 2); | |
| active_link = range; | |
| plotSingle(this); | |
| //gray out the others | |
| for (i = 0; i < legendClassArray.length; i++) { | |
| if (legendClassArray[i] != active_link) { | |
| d3.select("#id" + legendClassArray[i]) | |
| .style("opacity", 0.5); | |
| } | |
| } | |
| } else { //deactivate | |
| if (active_link === this.getAttribute('class').split("class").pop()) {//active square selected; turn it OFF | |
| d3.select(`#id${range}`) | |
| .style("stroke", "none") | |
| active_link = "0"; //reset | |
| //restore remaining boxes to normal opacity | |
| for (i = 0; i < legendClassArray.length; i++) { | |
| d3.select("#id" + legendClassArray[i]) | |
| .style("opacity", 1); | |
| } | |
| //restore plot to original | |
| restorePlot(d); | |
| } | |
| } //end active_link check | |
| }); | |
| function restorePlot(d) { | |
| state.selectAll("rect").forEach(function (d, i) { | |
| //restore shifted bars to original posn | |
| d3.select(d[idx]) | |
| .transition() | |
| .duration(1000) | |
| .attr("y", y_orig[i]); | |
| }) | |
| //restore opacity of erased bars | |
| for (i = 0; i < legendClassArray.length; i++) { | |
| if (legendClassArray[i] != class_keep) { | |
| d3.selectAll(".class" + legendClassArray[i]) | |
| .transition() | |
| .duration(1000) | |
| .delay(750) | |
| .style("opacity", 1); | |
| } | |
| } | |
| } | |
| function plotSingle(d) { | |
| class_keep = d.id ? d.id.split("id").pop() : d.getAttribute('class').split("class").pop() | |
| idx = legendClassArray.indexOf(class_keep); | |
| //erase all but selected bars by setting opacity to 0 | |
| for (i = 0; i < legendClassArray.length; i++) { | |
| if (legendClassArray[i] != class_keep) { | |
| d3.selectAll(".class" + legendClassArray[i]) | |
| .transition() | |
| .duration(1000) | |
| .style("opacity", 0); | |
| } | |
| } | |
| //lower the bars to start on x-axis | |
| y_orig = []; | |
| state.selectAll("rect").forEach(function (d, i) { | |
| //get height and y posn of base bar and selected bar | |
| h_keep = d3.select(d[idx]).attr("height"); | |
| y_keep = d3.select(d[idx]).attr("y"); | |
| //store y_base in array to restore plot | |
| y_orig.push(y_keep); | |
| h_base = d3.select(d[0]).attr("height"); | |
| y_base = d3.select(d[0]).attr("y"); | |
| h_shift = h_keep - h_base; | |
| y_new = y_base - h_shift; | |
| //reposition selected bars | |
| d3.select(d[idx]) | |
| .transition() | |
| .ease("bounce") | |
| .duration(1000) | |
| .delay(750) | |
| .attr("y", y_new); | |
| }) | |
| } | |
| }); | |
| </script> |