Skip to content

Instantly share code, notes, and snippets.

@pjsier
Last active June 30, 2017 20:34
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save pjsier/0c9406cda2c5dcb10b817691b699ad81 to your computer and use it in GitHub Desktop.
Save pjsier/0c9406cda2c5dcb10b817691b699ad81 to your computer and use it in GitHub Desktop.
SSL Treemap
license: mit
height: 800
border: no

SSL Treemap Visualization

Resizing treemap visualization of demographics of SSL scores with slider for adjusting the score.

<!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>
// 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