Skip to content

Instantly share code, notes, and snippets.

@eliot84
Created March 21, 2016 02:16
Show Gist options
  • Save eliot84/11d3ec2710e324af75a4 to your computer and use it in GitHub Desktop.
Save eliot84/11d3ec2710e324af75a4 to your computer and use it in GitHub Desktop.
Week 9: Stacked Transition
<!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.
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