Resizing treemap visualization of demographics of SSL scores with slider for adjusting the score.
Last active
June 30, 2017 20:34
-
-
Save pjsier/0c9406cda2c5dcb10b817691b699ad81 to your computer and use it in GitHub Desktop.
SSL Treemap
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
license: mit | |
height: 800 | |
border: no |
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> | |
<html> | |
<head> | |
<title>SSL Demographic Treemap</title> | |
<meta name="viewport" content="initial-scale=1.0, user-scalable=no" /> | |
<meta charset='utf-8' /> | |
<script src="https://d3js.org/d3.v4.min.js"></script> | |
<script src="https://d3js.org/d3-queue.v3.min.js"></script> | |
<script src="https://d3js.org/d3-hexbin.v0.2.min.js"></script> | |
<script src="https://d3js.org/colorbrewer.v1.min.js"></script> | |
<script src="https://d3js.org/d3-scale-chromatic.v0.3.min.js"></script> | |
<style> | |
#container { | |
width: 700px; | |
} | |
h4 span, #tooltip { | |
font-family: "Helvetica Neue", Helvetica, sans-serif; | |
} | |
svg { | |
font-family: "Helvetica Neue", Helvetica, sans-serif; | |
font-size: 10px; | |
} | |
#chart { | |
display: block; | |
width: 100%; | |
height: 600px; | |
} | |
g.background text { | |
stroke-linejoin: round; | |
stroke: white; | |
stroke-width: 2px; | |
} | |
g.titles text, | |
g.background text { | |
text-anchor: middle; | |
font-size: 12px; | |
font-weight: bold; | |
} | |
#scoreInput { | |
width: 100%; | |
} | |
#indivHeader { | |
float: right; | |
} | |
</style> | |
</head> | |
<body> | |
<div id="container"> | |
<h4> | |
<span>SSL Score >= <span id="scoreVal">0</span></span> | |
<span id="indivHeader"><span id="totalVal"></span> People</span> | |
</h4> | |
<input type="range" min="0" max="500" value="0" id="scoreInput"> | |
<svg id="chart" width="500" height="450"></svg> | |
</div> | |
<script src="script.js"></script> | |
</body> | |
</html> |
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
// Adapted from: | |
// - https://bl.ocks.org/mbostock/4063582 | |
// - https://bl.ocks.org/mbostock/2838bf53e0e65f369f476afd653663a2 | |
// Margin convention from https://bl.ocks.org/mbostock/3019563 | |
var margin = {top: 10, right: 10, bottom: 10, left: 10}; | |
var width = parseInt(d3.select("#chart").style("width")) - margin.left - margin.right; | |
var height = parseInt(d3.select("#chart").style("height")) - margin.top - margin.bottom; | |
var svg = d3.select("#chart") | |
.attr("width", width + margin.left + margin.right) | |
.attr("height", height + margin.top + margin.bottom) | |
.append("g") | |
.attr("transform", "translate(" + margin.left + "," + margin.top + ")"); | |
var scoreNum = 0; | |
var csvData, root; | |
var fader = function(color) { return d3.interpolateRgb(color, "#fff")(0.2); }, | |
color = d3.scaleOrdinal(d3.schemePaired.map(fader)), | |
format = d3.format(",d"); | |
var treemap = d3.treemap() | |
.tile(d3.treemapSquarify.ratio(1)) | |
.size([width, height]) | |
.round(true) | |
.paddingInner(1); | |
function manageTooltip(sel) { | |
var tooltip = d3.select("#tooltip"); | |
sel.on("mouseover", function(d){ | |
tooltip.style("visibility", "visible") | |
.html("<p>" + d.parent.parent.data.key + " " + d.parent.data.key + " " + | |
d.data.key + "<br><b>Count</b>: " + format(d.data.value) + "</p>") | |
}) | |
.on("mousemove", function(){ | |
var pagePad = 10; | |
var elPad = 200; | |
if (event.pageY < (window.innerHeight - elPad)) { | |
tooltip.style("bottom", null); | |
tooltip.style("top", (event.pageY-pagePad)+"px"); | |
} | |
else { | |
tooltip.style("top", null); | |
tooltip.style("bottom", (window.innerHeight-event.pageY+pagePad)+"px"); | |
} | |
if (event.pageX < (window.innerWidth - elPad)) { | |
tooltip.style("right", null); | |
tooltip.style("left", (event.pageX + 10)+"px"); | |
} | |
else { | |
tooltip.style("left", null); | |
tooltip.style("right", (window.innerWidth-event.pageX+pagePad)+"px"); | |
} | |
}) | |
.on("mouseout", function(){ tooltip.style("visibility", "hidden"); }); | |
} | |
function updateLabels() { | |
var backG = svg.select("g.background"); | |
var titleG = svg.select("g.titles"); | |
backG.selectAll("text").remove(); | |
titleG.selectAll("text").remove(); | |
root.children.forEach(function(c1) { | |
c1.children.forEach(function(c2) { | |
// For larger segments, include for both men and women | |
if (["black", "white", "hispanic"].indexOf(c1.data.key.split(".")[0].toLowerCase()) !== -1) { | |
var titleTransform = "translate(" + c2.x0 + "," + c2.y0 + ")"; | |
titleG.append("text") | |
.attr("transform", titleTransform) | |
.attr("x", (c2.x1 - c2.x0)/2) | |
.attr("y", ((c2.y1 - c2.y0)/2)-7) | |
.text(c1.data.key); | |
titleG.append("text") | |
.attr("transform", titleTransform) | |
.attr("x", (c2.x1 - c2.x0)/2) | |
.attr("y", ((c2.y1 - c2.y0)/2)+7) | |
.text(c2.data.key); | |
backG.append("text") | |
.attr("class", "background") | |
.attr("transform", titleTransform) | |
.attr("x", (c2.x1 - c2.x0)/2) | |
.attr("y", ((c2.y1 - c2.y0)/2)-7) | |
.text(c1.data.key); | |
backG.append("text") | |
.attr("class", "background") | |
.attr("transform", titleTransform) | |
.attr("x", (c2.x1 - c2.x0)/2) | |
.attr("y", ((c2.y1 - c2.y0)/2)+7) | |
.text(c2.data.key); | |
} | |
}); | |
}); | |
} | |
function updateData() { | |
var data = csvData; | |
var scoreData = data.filter(function(d) { return (d.ssl_score >= scoreNum) && (d.sex != "Other"); }); | |
d3.select("#totalVal").text(format(d3.sum(scoreData, function(d) { return d.count; }))); | |
var scoreNest = d3.nest() | |
.key(function(d) { return d.race; }) | |
.key(function(d) { return d.sex; }) | |
.key(function(d) { return d.age; }) | |
.rollup(function(d) { return d3.sum(d, function(d) { return +d.count; }); }); | |
root = d3.hierarchy({values: scoreNest.entries(scoreData)}, function(d) { return d.values; }) | |
.eachBefore(function(d) { | |
if (d.data.key) { | |
d.data.id = (d.parent ? d.parent.data.id + "." : "") + d.data.key.toLowerCase(); | |
} | |
else { | |
d.data.id = "ssl"; | |
} | |
}) | |
.sum(function(d) { return d.value; }) | |
.sort(function(a, b) { return b.height - a.height || b.value - a.value; }); | |
updateLayout(); | |
} | |
function updateLayout() { | |
width = parseInt(d3.select("#chart").style("width")) - margin.left - margin.right, | |
height = parseInt(d3.select("#chart").style("height")) - margin.top - margin.bottom; | |
treemap.size([width, height]); | |
treemap(root); | |
var leafG = svg.select("g.leafgroup") | |
var leaves = leafG.selectAll("g.leaves") | |
.data(root.leaves(), function(d) { return d.data.id; }); | |
leaves.exit().remove(); | |
// RE-ADD INFORMATION FOR NEW DATA | |
var leavesEnter = leaves.enter() | |
.append("g") | |
.attr("class", "leaves") | |
.attr("transform", function(d) { return "translate(" + d.x0 + "," + d.y0 + ")"; }) | |
.call(manageTooltip); | |
var rectLeaves = leavesEnter.append("rect") | |
.attr("id", function(d) { return d.data.id; }) | |
.attr("class", function(d) { return d.data.id.split(".").join(" "); }) | |
.attr("fill", function(d) { return color(d.parent.data.id); }) | |
.attr("width", function(d) { return d.x1 - d.x0; }) | |
.attr("height", function(d) { return d.y1 - d.y0; }); | |
leavesEnter.append("clipPath") | |
.attr("id", function(d) { return "clip-" + d.data.id; }) | |
.append("use") | |
.attr("xlink:href", function(d) { return "#" + d.data.id; }); | |
leavesEnter.append("text") | |
.attr("clip-path", function(d) { return "url(#clip-" + d.data.id + ")"; }) | |
.attr("x", 4) | |
.attr("y", 14) | |
.text(function(d) { return d.data.key; }); | |
updateLabels(); | |
// UPDATE SELECTION FOR CHANGED INFO | |
var leavesTransition = leaves.transition() | |
.duration(300) | |
.ease(d3.easeQuadInOut) | |
.attr("transform", function(d) { return "translate(" + d.x0 + "," + d.y0 + ")"; }) | |
.select("rect") | |
.attr("width", function(d) { return d.x1 - d.x0; }) | |
.attr("height", function(d) { return d.y1 - d.y0; }); | |
} | |
d3.csv("https://raw.githubusercontent.com/pjsier/strategic-subject-list-viz/master/data/treemap_ssl_groups.csv", function(error, data) { | |
csvData = data; | |
updateData(); | |
width = parseInt(d3.select("#chart").style("width")) - margin.left - margin.right, | |
height = parseInt(d3.select("#chart").style("height")) - margin.top - margin.bottom; | |
treemap.size([width, height]); | |
treemap(root); | |
var leafG = svg.append("g").attr("class", "leafgroup") | |
svg.append("g").attr("class", "background"); | |
svg.append("g").attr("class", "titles"); | |
var tooltip = d3.select("body") | |
.append("div") | |
.attr("id", "tooltip") | |
.style("visibility", "hidden") | |
.style("font-size", "16px") | |
.style("padding", "10px") | |
.style("z-index", "10") | |
.style("position", "absolute") | |
.style("background-color", "rgba(221,221,221,0.8)"); | |
var leaves = leafG.selectAll("g.leaves") | |
.data(root.leaves(), function(d) { return d.data.id; }) | |
.enter() | |
.append("g") | |
.attr("class", "leaves") | |
.attr("transform", function(d) { return "translate(" + d.x0 + "," + d.y0 + ")"; }) | |
.call(manageTooltip); | |
leaves.append("rect") | |
.attr("id", function(d) { return d.data.id; }) | |
.attr("class", function(d) { return d.data.id.split(".").join(" "); }) | |
.attr("width", function(d) { return d.x1 - d.x0; }) | |
.attr("height", function(d) { return d.y1 - d.y0; }) | |
.attr("fill", function(d) { return color(d.parent.data.id); }); | |
leaves.append("clipPath") | |
.attr("id", function(d) { return "clip-" + d.data.id; }) | |
.append("use") | |
.attr("xlink:href", function(d) { return "#" + d.data.id; }); | |
leaves.append("text") | |
.attr("clip-path", function(d) { return "url(#clip-" + d.data.id + ")"; }) | |
.attr("x", 4) | |
.attr("y", 14) | |
.text(function(d) { return d.data.key; }); | |
updateLabels(); | |
d3.select(window).on("resize", updateData); | |
d3.select("#scoreInput").on("input", function () { | |
var el = document.getElementById("scoreInput"); | |
scoreNum = +el.value; | |
d3.select("#scoreVal").text(scoreNum); | |
updateData(); | |
}); | |
}); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment