Skip to content

Instantly share code, notes, and snippets.

@And-How
Last active December 14, 2015 11:19
Show Gist options
  • Select an option

  • Save And-How/5077820 to your computer and use it in GitHub Desktop.

Select an option

Save And-How/5077820 to your computer and use it in GitHub Desktop.
grouped to stacked bar chart modified from http://bl.ocks.org/3943967
<!DOCTYPE html>
<meta charset="utf-8">
<head>
<title>Stacked and Grouped Bar with Animation</title>
<!--modified from http://bl.ocks.org/3943967-->
<style>
body {
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
margin: auto;
position: relative;
width: 960px;
}
h1 {
font-size: 40px;
font-weight: 300;
letter-spacing: -2px;
margin: .3em 0 .1em 40px;
}
h2 {
text-align: right;
margin: 0 30px 0 0;
}
text {
font: 20px sans-serif;
}
.yaxis path,
.yaxis line {
fill: none;
stroke: black;
shape-rendering: crispEdges;
}
.axis path,
.axis line {
fill: none;
stroke: black;
shape-rendering: crispEdges;
}
form {
position: absolute;
right: 10px;
top: 10px;
}
legend {
background-color: #fff;
width: 300px;
height: 60px;
border: 1px solid #bbb;
margin: 10px;
}
</style>
</head>
<body>
<h1>Stacked to Grouped Bar Chart</h1>
<form>
</form>
<div>
<h2>Legend</h2> <br>
</div>
<script src="http://d3js.org/d3.v3.js"></script>
<script>
d3.select(self.frameElement).style("height", "1000px");
d3.select(self.frameElement).style("width", "1000px");
var sampledata = [
[
{ "employmenttype": "retail","dwellingunittype": "single-family", "scenarioname":"Base", "x": 0, "y": 9},
{ "employmenttype": "office","dwellingunittype": "attached single-family","scenarioname":"Scenario 1", "x": 1, "y": 29},
{ "employmenttype": "industrial","dwellingunittype": "detached single-family","scenarioname":"Scenario 2","x": 2, "y": 30},
{ "employmenttype": "public","dwellingunittype": "multifamily","scenarioname":"Scenario 3","x": 3, "y": 35},
{ "employmenttype": "civic","dwellingunittype": "yurt","scenarioname":"Scenario 4","x": 4, "y": 10}
],
[
{ "employmenttype": "retail","dwellingunittype": "single-family","scenarioname":"Base", "x": 0, "y": 25},
{ "employmenttype": "office","dwellingunittype": "attached single-family","scenarioname":"Scenario 1", "x": 1, "y": 30},
{ "employmenttype": "industrial","dwellingunittype": "detached single-family","scenarioname":"Scenario 2","x": 2, "y": 40},
{ "employmenttype": "public","dwellingunittype": "multifamily","scenarioname":"Scenario 3","x": 3, "y": 12},
{ "employmenttype": "civic","dwellingunittype": "yurt","scenarioname":"Scenario 4","x": 4, "y": 12}
],
[
{ "employmenttype": "retail", "dwellingunittype": "single-family","scenarioname":"Base", "x": 0, "y": 30},
{ "employmenttype": "office","dwellingunittype": "attached single-family","scenarioname":"Scenario 1", "x": 1, "y": 12},
{ "employmenttype": "industrial","dwellingunittype": "detached single-family","scenarioname":"Scenario 2","x": 2, "y": 28},
{ "employmenttype": "public","dwellingunittype": "multifamily","scenarioname":"Scenario 3","x": 3, "y": 16},
{ "employmenttype": "civic","dwellingunittype": "yurt","scenarioname":"Scenario 4","x": 4, "y": 16}
],
[
{ "employmenttype": "retail", "dwellingunittype": "single-family","scenarioname":"Base", "x": 0, "y": 25},
{ "employmenttype": "office","dwellingunittype": "attached single-family","scenarioname":"Scenario 1", "x": 1, "y": 39},
{ "employmenttype": "industrial","dwellingunittype": "detached single-family","scenarioname":"Scenario 2","x": 2, "y": 30},
{ "employmenttype": "public","dwellingunittype": "multifamily","scenarioname":"Scenario 3","x": 3, "y": 25},
{ "employmenttype": "civic","dwellingunittype": "yurt","scenarioname":"Scenario 4","x": 4, "y": 16}
],
[
{ "employmenttype": "retail", "dwellingunittype": "single-family","scenarioname":"Base", "x": 0, "y": 6},
{ "employmenttype": "office","dwellingunittype": "attached single-family","scenarioname":"Scenario 1", "x": 1, "y": 29},
{ "employmenttype": "industrial","dwellingunittype": "detached single-family","scenarioname":"Scenario 2","x": 2, "y": 30},
{ "employmenttype": "public","dwellingunittype": "multifamily","scenarioname":"Scenario 3","x": 3, "y": 30},
{ "employmenttype": "civic","dwellingunittype": "yurt","scenarioname":"Scenario 4","x": 4, "y": 40}
]
];
var scenarionames = sampledata[1].map(function(d){return d.scenarioname}),//returns array of scenarioname values
numberofscenarios = scenarionames.length, //number of scenarios
dwellingunittypelabels = sampledata[0].map(function(d){return d.dwellingunittype}),//returns array of dwellingunittype values
employmentypelabels = sampledata[0].map(function(d){return d.employmenttype}),//returns array of employmenttype values
markervalue = 400;
var stack = d3.layout.stack(),
layers = stack(sampledata),//creates stack layers from data
yGroupMax = d3.max(layers, function(layer) { return d3.max(layer, function(d) { return d.y; }); }),//calculates max for grouped graph
yStackMax = d3.max(layers, function(layer) { return d3.max(layer, function(d) { return d.y0 + d.y; }); });//calculates max for stacked graph
//sets margins
var margin = {top: 40, right: 10, bottom: 20, left: 40},
width = 960 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
//sets x-scale to draw bars
var x = d3.scale.ordinal()
.domain(d3.range(numberofscenarios))
.rangeRoundBands([30, width],.08);//sets bands for labels and ratio of groups to space between
//sets x-scale for labeled axis
var xlabels = d3.scale.ordinal()
.domain(scenarionames)
.rangeRoundBands([0, width], .08);
//sets y-scale for stacked graph
var y = d3.scale.linear()
.domain([0, yStackMax])
.range([height, 0]);
//sets y-scale for grouped graph
var yGroupedScale = d3.scale.linear()
.domain([0, yGroupMax])
.range([height, 0]);
//sets color scale
var color = d3.scale.linear()
.domain([0, layers.length - 1])
.range([ "yellow","green"]);
//sets x-Axis
var xAxis = d3.svg.axis()
.scale(xlabels)
.tickSize(0)
.tickPadding(6)
.orient("bottom");
//sets y-axis for stacked graph
var yStackedAxis = d3.svg.axis()
.scale(y)
.tickSize(5)
.tickPadding(0)
.orient("left");
//sets y-axis for grouped graph
var yGroupedAxis = d3.svg.axis()
.scale(yGroupedScale)
.tickSize(5)
.tickPadding(0)
.orient("left");
//draws box to draw graph in
var 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 + ")");
//assigns color to layers
var layer = svg.selectAll(".layer")
.data(layers)
.enter().append("g")
.attr("class", "layer")
.style("fill", function(d, i) { return color(i); })
.style("stroke","white")
.style("stroke-width", "3");
//adding control total guide
var marker = layer.selectAll("line")
.data(function(d) {return d;})
.enter().append("line")
.attr("x1", function(d) { return x(d.x); })
.attr("x2", function(d) { return x(d.x) + x.rangeBand(); })
.attr("y1", markervalue)
.attr("y2", markervalue)
.style("stroke","red")
.style("stroke-width", "3");
//draws rectangles
var rect = layer.selectAll("rect")
.data(function(d) { return d; })
.enter().append("rect")
.attr("x", function(d) { return x(d.x); })
.attr("y", height)
.attr("width", x.rangeBand())
.attr("height", 0);
//adds label on hover
// know I should add tooltip a-la https://gist.github.com/biovisualize/1016860
rect.append("title")
.attr("x", function(d) { return x(d.x); })
.attr("y", height)
.text(function(d) {return d.y;});
//opening transition, draws stacked bars up from ground
rect.transition()
.delay(function(d, i) { return i * 10; })//staggers drawing
.attr("y", function(d) { return y(d.y0 + d.y); })
.attr("height", function(d) { return y(d.y0) - y(d.y0 + d.y); });
//draw x-Axis
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
//draw Stacked y-Axis
svg.append("g")
.attr("class", "yaxis")
.attr("transform", "translate("+ margin.left +",0)")
.call(yStackedAxis)
//re-draw Grouped y-Axis
.transition()
.delay(2000).duration(1000)
.call(yGroupedAxis);
//sets legend margins
var legendmargin = {top: 40, right: 10, bottom: 20, left: 40},
legendwidth = 960 -30,
legendheight = 200 - legendmargin.top - legendmargin.bottom;
//draws legend box
var legendsvg = d3.select("div").append("svg")
.attr("width", legendwidth)
.attr("height", legendheight)
.append("g");
//sets data for legends
var legend = legendsvg.selectAll("legend")
.data(employmentypelabels.slice()) //copies scenarionames array in reverse order
.enter().append("g")
.attr("class", "legend")
.attr("transform", function(d,i) { return "translate(0," + i * 20 + ")"; });//20 pixel between boxes 'i' is iterator
//draws colored boxes
legend.append("rect")//ads rectangles to legend svg.
.data(layers)
.attr("x", legendwidth - 20) //18 pixel boxes (2 pixels between)
.attr("width", 20)
.attr("height", 20)
.style("fill", function(d, i) { return color(i); })//fills with corresponding color
.style("stroke","white")
.style("stroke-width", "3");
//adds legend labels
legend.append("text")
.attr("x", legendwidth - 24) // adds text 24 pixels away from end of svg
.attr("y", 9)
.attr("dy", ".35em")
.style("text-anchor", "end") //right adjust
.text(function(d) { return d; }); //fills text with name from scenarionames array
var button = d3.selectAll("form")
.append("label")
.append("input")
.attr("type", "radio")
.attr("name","mode")
.attr("value", "grouped")
.text("grouped");
var button2 = d3.selectAll("form").append("label")
.append("input")
.attr("type", "radio")
.attr("name","mode")
.attr("value", "stacked")
.text("stacked");
//button response
d3.selectAll("input").on("change", change);//input changes on change event
var timeout = setTimeout(function() {
d3.select("input[value=\"grouped\"]").property("checked", true).each(change);
}, 2000);
function change() {
clearTimeout(timeout);
if (this.value === "grouped") transitionGrouped();//if grouped is checked transition to grouped
else transitionStacked();//otherwise
}
function transitionGrouped() {
y.domain([0, yGroupMax]);//sets to grouped domain (rather than stacked)
rect.transition()//bartransition
.duration(500)
.delay(function(d, i) { return i * 10; })
.attr("x", function(d, i, j) {
return x(d.x) + x.rangeBand() / layers.length * j; })
.attr("width", x.rangeBand() / layers.length)
.transition()
.attr("y", function(d) { return y(d.y); })
.attr("height", function(d) { return height - y(d.y); });
svg.select(".yaxis")//y-axis transition
.transition()
.duration(500)
.call(yGroupedAxis);
}
function transitionStacked() {
y.domain([0, yStackMax]);
rect.transition()
.duration(500)
.delay(function(d, i) { return i * 10; })
.attr("y", function(d) { return y(d.y0 + d.y); })
.attr("height", function(d) { return y(d.y0) - y(d.y0 + d.y); })
.transition()
.attr("x", function(d) { return x(d.x); })
.attr("width", x.rangeBand());
svg.select(".yaxis")
.transition()
.duration(500)
.call(yStackedAxis);
}
</script>
</body>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment