D3
OG
Old school D3 from simpler times
All examples
By author
By category
About
michalmatwie
Full window
Github gist
stacked/grouped
Built with
blockbuilder.org
<!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>
https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.13/d3.min.js