|
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN"> |
|
<html> |
|
<head> |
|
<meta charset="utf-8"> |
|
<style type="text/css"> |
|
rect.bordered { |
|
stroke: #E6E6E6; |
|
stroke-width: 2px; |
|
} |
|
|
|
body { |
|
font-size: 9pt; |
|
font-family: Consolas, courier; |
|
} |
|
|
|
text.axis { |
|
fill: #000; |
|
} |
|
|
|
.axis path, |
|
.axis line { |
|
fill: none; |
|
stroke: #000; |
|
shape-rendering: crispEdges; |
|
} |
|
|
|
.bar { |
|
fill: steelblue; |
|
} |
|
|
|
.x.axis path { |
|
display: none; |
|
} |
|
|
|
.legend line { |
|
stroke: #000; |
|
shape-rendering: crispEdges; |
|
} |
|
|
|
form { |
|
position: absolute; |
|
right: 10px; |
|
top: 10px; |
|
} |
|
</style> |
|
<script src="http://d3js.org/d3.v3.js" type="text/javascript"></script> |
|
<title></title> |
|
</head> |
|
<body> |
|
<form> |
|
<label><input type="radio" name="mode" value="bypercent" checked> Percent</label> |
|
<label><input type="radio" name="mode" value="bycount"> Number of Respondants</label> |
|
</form> |
|
|
|
<div id="chart"></div> |
|
|
|
<script type="text/javascript"> |
|
|
|
var margin = {top: 20, right: 231, bottom: 140, left: 40}, |
|
width = 1000 - margin.left - margin.right, |
|
height = 800 - margin.top - margin.bottom; |
|
|
|
var xscale = d3.scale.ordinal() |
|
.rangeRoundBands([0, width], .1); |
|
|
|
var yscale = d3.scale.linear() |
|
.rangeRound([height, 0]); |
|
|
|
var colors = d3.scale.ordinal() |
|
.range(["#63c172", "#ee9952", "#46d6c4", "#fee851", "#98bc9a"]); |
|
|
|
var xaxis = d3.svg.axis() |
|
.scale(xscale) |
|
.orient("bottom"); |
|
|
|
var yaxis = d3.svg.axis() |
|
.scale(yscale) |
|
.orient("left") |
|
.tickFormat(d3.format(".0%")); // ** |
|
|
|
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 + ")"); |
|
|
|
// load and handle the data |
|
d3.tsv("data.tsv", function(error, data) { |
|
|
|
// rotate the data |
|
var categories = d3.keys(data[0]).filter(function(key) { return key !== "Absolutes"; }); |
|
var parsedata = categories.map(function(name) { return { "Absolutes": name }; }); |
|
data.forEach(function(d) { |
|
parsedata.forEach(function(pd) { |
|
pd[d["Absolutes"]] = d[pd["Absolutes"]]; |
|
}); |
|
}); |
|
|
|
// map column headers to colors (except for 'Absolutes' and 'Base: All Respondents') |
|
colors.domain(d3.keys(parsedata[0]).filter(function(key) { return key !== "Absolutes" && key !== "Base: All Respondents"; })); |
|
|
|
// add a 'responses' parameter to each row that has the height percentage values for each rect |
|
parsedata.forEach(function(pd) { |
|
var y0 = 0; |
|
// colors.domain() is an array of the column headers (text) |
|
// pd.responses will be an array of objects with the column header |
|
// and the range of values it represents |
|
pd.responses = colors.domain().map(function(response) { |
|
var responseobj = {response: response, y0: y0, yp0: y0}; |
|
y0 += +pd[response]; |
|
responseobj.y1 = y0; |
|
responseobj.yp1 = y0; |
|
return responseobj; |
|
}); |
|
// y0 is now the sum of all the values in the row for this category |
|
// convert the range values to percentages |
|
pd.responses.forEach(function(d) { d.yp0 /= y0; d.yp1 /= y0; }); |
|
// save the total |
|
pd.totalresponses = pd.responses[pd.responses.length - 1].y1; |
|
}); |
|
|
|
// sort by the value in 'Right Direction' |
|
// parsedata.sort(function(a, b) { return b.responses[0].yp1 - a.responses[0].yp1; }); |
|
|
|
// ordinal-ly map categories to x positions |
|
xscale.domain(parsedata.map(function(d) { return d.Absolutes; })); |
|
|
|
// add the x axis and rotate its labels |
|
svg.append("g") |
|
.attr("class", "x axis") |
|
.attr("transform", "translate(0," + height + ")") |
|
.call(xaxis) |
|
.selectAll("text") |
|
.attr("y", 5) |
|
.attr("x", 7) |
|
.attr("dy", ".35em") |
|
.attr("transform", "rotate(65)") |
|
.style("text-anchor", "start"); |
|
|
|
// add the y axis |
|
svg.append("g") |
|
.attr("class", "y axis") |
|
.call(yaxis); |
|
|
|
// create svg groups ("g") and place them |
|
var category = svg.selectAll(".category") |
|
.data(parsedata) |
|
.enter().append("g") |
|
.attr("class", "category") |
|
.attr("transform", function(d) { return "translate(" + xscale(d.Absolutes) + ",0)"; }); |
|
|
|
// draw the rects within the groups |
|
category.selectAll("rect") |
|
.data(function(d) { return d.responses; }) |
|
.enter().append("rect") |
|
.attr("width", xscale.rangeBand()) |
|
.attr("y", function(d) { return yscale(d.yp1); }) |
|
.attr("height", function(d) { return yscale(d.yp0) - yscale(d.yp1); }) |
|
.style("fill", function(d) { return colors(d.response); }); |
|
|
|
// position the legend elements |
|
var legend = svg.selectAll(".legend") |
|
.data(colors.domain()) |
|
.enter().append("g") |
|
.attr("class", "legend") |
|
.attr("transform", function(d, i) { return "translate(20," + ((height - 18) - (i * 20)) + ")"; }); |
|
|
|
legend.append("rect") |
|
.attr("x", width - 18) |
|
.attr("width", 18) |
|
.attr("height", 18) |
|
.style("fill", colors); |
|
|
|
legend.append("text") |
|
.attr("x", width + 10) |
|
.attr("y", 9) |
|
.attr("dy", ".35em") |
|
.style("text-anchor", "start") |
|
.text(function(d) { return d; }); |
|
|
|
// animation |
|
d3.selectAll("input").on("change", handleFormClick); |
|
|
|
function handleFormClick() { |
|
if (this.value === "bypercent") { |
|
transitionPercent(); |
|
} else { |
|
transitionCount(); |
|
} |
|
} |
|
|
|
// transition to 'percent' presentation |
|
function transitionPercent() { |
|
// reset the yscale domain to default |
|
yscale.domain([0, 1]); |
|
|
|
// create the transition |
|
var trans = svg.transition().duration(250); |
|
|
|
// transition the bars |
|
var categories = trans.selectAll(".category"); |
|
categories.selectAll("rect") |
|
.attr("y", function(d) { return yscale(d.yp1); }) |
|
.attr("height", function(d) { return yscale(d.yp0) - yscale(d.yp1); }); |
|
|
|
// change the y-axis |
|
// set the y axis tick format |
|
yaxis.tickFormat(d3.format(".0%")); |
|
svg.selectAll(".y.axis").call(yaxis); |
|
} |
|
|
|
// transition to 'count' presentation |
|
function transitionCount() { |
|
// set the yscale domain |
|
yscale.domain([0, d3.max(parsedata, function(d) { return d.totalresponses; })]); |
|
|
|
// create the transition |
|
var transone = svg.transition() |
|
.duration(250); |
|
|
|
// transition the bars (step one) |
|
var categoriesone = transone.selectAll(".category"); |
|
categoriesone.selectAll("rect") |
|
.attr("y", function(d) { return this.getBBox().y + this.getBBox().height - (yscale(d.y0) - yscale(d.y1)) }) |
|
.attr("height", function(d) { return yscale(d.y0) - yscale(d.y1); }); |
|
|
|
// transition the bars (step two) |
|
var transtwo = transone.transition() |
|
.delay(350) |
|
.duration(350) |
|
.ease("bounce"); |
|
var categoriestwo = transtwo.selectAll(".category"); |
|
categoriestwo.selectAll("rect") |
|
.attr("y", function(d) { return yscale(d.y1); }); |
|
|
|
// change the y-axis |
|
// set the y axis tick format |
|
yaxis.tickFormat(d3.format(".2s")); |
|
svg.selectAll(".y.axis").call(yaxis); |
|
} |
|
}); |
|
|
|
d3.select(self.frameElement).style("height", (height + margin.top + margin.bottom) + "px"); |
|
|
|
</script> |
|
</body> |
|
</html> |