Created
March 21, 2016 02:16
-
-
Save eliot84/11d3ec2710e324af75a4 to your computer and use it in GitHub Desktop.
Week 9: Stacked Transition
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!DOCTYPE html> | |
<!-- code loosely inspired by this block https://gist.github.com/mstanaland/6100713 --> | |
<meta charset="utf-8"> | |
<style> | |
body { | |
font: 12px sans-serif; | |
padding: 50px; | |
} | |
#form { | |
position: relative; | |
right: 10px; | |
top: 10px; | |
padding-bottom: 20px; | |
} | |
.axis path, | |
.axis line { | |
fill: none; | |
stroke: #000; | |
shape-rendering: crispEdges; | |
} | |
.bar { | |
fill: black; | |
} | |
.x.axis path { | |
display: none; | |
} | |
.tooltip { | |
position: absolute; | |
z-index: 10; | |
} | |
.tooltip p { | |
background-color: white; | |
border: gray 1px solid; | |
padding: 2px; | |
max-width: 180px; | |
} | |
</style> | |
<body> | |
<h2>Top 20 Olympic Medal Recipient Countries</h2> | |
<p><a href="http://www.olympic.it/english/medal">Olympic.it: </a> The below shows the total medals earned and type by the top 20 countries with the highest numbers of medals won.</p> | |
<div id="form"> | |
<label><input type="radio" name="mode" value="bycount" checked>Raw Count</label> | |
<label><input type="radio" name="mode" value="bypercent">As Percent of medals Received</label> | |
</div> | |
<div id="chart"></div> | |
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.6/d3.min.js"></script> | |
<script> | |
var currentMode = "bycount"; | |
var fullwidth = 980, fullheight = 500; | |
var margin = {top: 20, right: 150, bottom: 50, left: 40}, | |
width = fullwidth - margin.left - margin.right, | |
height = fullheight - margin.top - margin.bottom; | |
var xScale = d3.scale.ordinal() | |
.rangeRoundBands([0, width], .3); | |
var yScale = d3.scale.linear() | |
.rangeRound([height, 0]); | |
//var color = d3.scale.category20b(); | |
var color = d3.scale.ordinal() | |
.domain(["Bronze", "Silver", "Gold"]) | |
.range(["#965A38", "#A8A8A8" , "#C98910"]); | |
var xAxis = d3.svg.axis() | |
.scale(xScale) | |
.orient("bottom") | |
.innerTickSize([0]); | |
var yAxis = d3.svg.axis() | |
.scale(yScale) | |
.orient("left") | |
.tickFormat(d3.format(".2s")); // for the stacked totals version | |
var stack = d3.layout | |
.stack(); // default view is "zero" for the count display. | |
var svg = d3.select("#chart").append("svg") | |
.attr("width", fullwidth) | |
.attr("height", fullheight) | |
.append("g") | |
.attr("transform", "translate(" + margin.left + "," + margin.top + ")"); | |
var tooltip = d3.select("body").append("div").attr("class", "tooltip"); | |
d3.csv("olympicMedals.csv", function(error, data) { | |
if (error) { | |
console.log(error); | |
} | |
data.sort(function(a, b) {return d3.ascending(a.Country,b.Country);}); | |
// how would we sort by largest total bar? what would we have to calculate? | |
var medals = ["Bronze","Silver","Gold"]; | |
color.domain(medals); | |
xScale.domain(data.map(function(d) { return d.Country; })); | |
svg.append("g") | |
.attr("class", "x axis") | |
.attr("transform", "translate(0," + height + ")") | |
.call(xAxis) | |
.selectAll("text") | |
.attr("dy", ".5em") | |
.attr("transform", "rotate(-30)") | |
.style("text-anchor", "end"); | |
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("Received"); | |
transitionCount(); // this will use the by-count stack, and make the data, and draw. | |
drawLegend(); | |
d3.selectAll("input").on("change", handleFormClick); | |
// All the functions for stuff above! | |
function handleFormClick() { | |
if (this.value === "bypercent") { | |
currentMode = "bypercent"; | |
transitionPercent(); | |
} else { | |
currentMode = "bycount"; | |
transitionCount(); | |
} | |
} | |
function makeData(medals, data) { | |
return medals.map(function(medal) { | |
return data.map(function(d) { | |
return {x: d.Country, y: +d[medal], medal: medal}; | |
}) | |
}); | |
} | |
function transitionPercent() { | |
yAxis.tickFormat(d3.format("%")); | |
stack.offset("expand"); // use this to get it to be relative/normalized! | |
var stacked = stack(makeData(medals, data)); | |
// call function to do the bars, which is same across both formats. | |
transitionRects(stacked); | |
} | |
function transitionCount() { | |
yAxis.tickFormat(d3.format(".2s")); // for the stacked totals version | |
stack.offset("zero"); | |
var stacked = stack(makeData(medals, data)); | |
transitionRects(stacked); | |
} | |
function transitionRects(stacked) { | |
// this domain is using the last of the stacked arrays, which is the last medal, and getting the max height. | |
yScale.domain([0, d3.max(stacked[stacked.length-1], function(d) { return d.y0 + d.y; })]); | |
var medal = svg.selectAll("g.medal") | |
.data(stacked); | |
medal.enter().append("g") | |
.attr("class", "medal") | |
.style("fill", function(d, i) { return color(d[0].medal); }); | |
// then data for each, plus mouseovers - a nested selection/enter here | |
medal.selectAll("rect") | |
.data(function(d) { | |
console.log("array for a rectangle", d); | |
return d; }) // this just gets the array for bar segment. | |
.enter().append("rect") | |
.attr("width", xScale.rangeBand()) | |
.on("mouseover", mouseover) | |
.on("mousemove", mousemove) | |
.on("mouseout", mouseout); | |
// the thing that needs to transition is the rectangles themselves, not the g parent. | |
medal.selectAll("rect") | |
.transition() | |
.duration(250) | |
.attr("x", function(d) { | |
return xScale(d.x); }) | |
.attr("y", function(d) { | |
return yScale(d.y0 + d.y); }) // | |
.attr("height", function(d) { | |
return yScale(d.y0) - yScale(d.y0 + d.y); }); // height is base - tallness | |
medal.exit().remove(); // there's actually nothing removed here - we just transition. | |
svg.selectAll(".y.axis").transition().call(yAxis); | |
} | |
// Building a legend by hand, based on http://bl.ocks.org/mbostock/3886208 | |
function drawLegend() { | |
// reverse to get the same order as the bar color layers | |
var medals_reversed = medals.slice().reverse(); | |
var legend = svg.selectAll(".legend") | |
.data(medals_reversed) // make sure your labels are in the right order -- if not, use .reverse() here. | |
.enter().append("g") | |
.attr("class", "legend") | |
.attr("transform", function(d, i) { return "translate(0," + i * 20 + ")"; }); | |
legend.append("rect") | |
.attr("x", width) | |
.attr("width", 18) | |
.attr("height", 18) | |
.style("fill", function(d) {return color(d)}); | |
legend.append("text") | |
.attr("x", width + 24) | |
.attr("y", 9) | |
.attr("dy", ".35em") | |
.style("text-anchor", "start") | |
.text(function(d, i) { return medals_reversed[i].replace(/_/g, " "); }); | |
} | |
function mouseover(d) { | |
// this will highlight both a dot and its line. | |
var number; | |
d3.select(this) | |
.transition() | |
.style("stroke", "black"); | |
if (currentMode == "bypercent") { | |
number = d3.format(".1%")(d.y); | |
} else { | |
number = d.y; | |
} | |
tooltip | |
.style("display", null) // this removes the display none setting from it | |
.html("<p>Medal: " + d.medal.replace(/_/g, " ") + | |
"<br>Received: " + number + | |
"<br>Country: " + d.x + " </p>"); | |
} | |
function mousemove(d) { | |
tooltip | |
.style("top", (d3.event.pageY - 10) + "px" ) | |
.style("left", (d3.event.pageX + 10) + "px"); | |
} | |
function mouseout(d) { | |
d3.select(this) | |
.transition() | |
.style("stroke", "none"); | |
tooltip.style("display", "none"); // this sets it to invisible! | |
} | |
}); | |
</script> |
We can make this file beautiful and searchable if this error is corrected: It looks like row 21 should actually have 4 columns, instead of 5. in line 20.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Country,Gold,Silver,Bronze | |
USA,1073,860,751 | |
Germany,251,260,270 | |
England,247,276,283 | |
Italy,235,200,228 | |
France,232,254,292 | |
China,213,166,147 | |
Sweden,193,203,231 | |
Russia,182,167,179 | |
Norway,174,160,144 | |
Hungary,167,147,168 | |
Australia,143,156,180 | |
Finland,143,146,174 | |
Japan,140,142,161 | |
Canada,121,154,173 | |
Netherlands,114,123,138 | |
South Korea,107,99,90 | |
Switzerland,97,112,113 | |
Romania,88,94,119 | |
Austria,77,111,112 | |
Cuba,72,67,69,208 |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment