Created
February 1, 2018 16:27
-
-
Save anbnyc/a0b946d3d2dde50b424ab9904e9645cd to your computer and use it in GitHub Desktop.
Treemap based on NYC 2015 Tree Census
This file contains hidden or 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
<html> | |
<head> | |
<style> | |
.menu { | |
display: block; } | |
.menu select { | |
margin: 10px; } | |
.menu .legend { | |
margin: 10px; | |
position: relative; | |
display: inline-block; } | |
.menu .legend .legend-entry { | |
font: 14px sans-serif; | |
padding: 0 5 0 5; | |
margin: 0 5 0 5; | |
display: inline; | |
color: #eee; } | |
.treemap { | |
display: block; | |
position: relative; } | |
.treemap .node { | |
font: 10px sans-serif; | |
color: #eee; | |
line-height: 12px; | |
overflow: hidden; | |
position: absolute; | |
text-indent: 2px; } | |
.treemap .node .on-hover { | |
opacity: 0; | |
transition: opacity 250ms; } | |
.treemap .node:hover .on-hover { | |
opacity: 1; } | |
</style> | |
<script src="https://d3js.org/d3.v4.min.js"></script> | |
</head> | |
<body> | |
</body> | |
<script> | |
d3.json('https://gist.githubusercontent.com/anbnyc/37fcb61a8a5e3d89ba506089400180b4/raw/ea9ac412ab2a2a98c1df31d7c865998c696de27a/trees.json', function(err, data){ | |
const container = d3.select('body') | |
.append('div') | |
.attr('class','container') | |
.html(` | |
<div class="menu"></div> | |
<div class="treemap"></div> | |
`) | |
const healthStatuses = ['Good','Fair','Poor'] | |
const colorFn = d3.scaleOrdinal() | |
.domain(healthStatuses) | |
.range(['#080','#f80','#800']); | |
const treemapFn = d3.treemap() | |
.size([800, 600]) | |
.padding(1) | |
.round(true); | |
let nested = d3.nest() | |
.key(d => d.boroname) | |
.key(d => d.nta_name) | |
.key(d => d.spc_common.replace(/\w\S*/g, txt => txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase())) //SO 196972 | |
.key(d => d.health) | |
.rollup(values => values.reduce((t,v) => t+v.n,0)) | |
.entries(data.filter(d => !!d.spc_common && !!d.health)) | |
const borosNtas = nested.map(d => d.key).reduce((t,v,i) => ({ | |
...t, | |
[v]: nested[i].values.map(d => d.key) | |
}), {}) | |
let selectedBoro = Object.keys(borosNtas)[0] | |
let selectedNta = borosNtas[selectedBoro][0] | |
function dataLoaded(){ | |
const boro = d3.select('.menu') | |
.selectAll('.select-boro') | |
.data([null]) | |
.enter().append('select') | |
.attr('class','select-boro') | |
.on('change', update) | |
boro.selectAll('option.option-boro') | |
.data(Object.keys(borosNtas), d => d) | |
.enter().append('option') | |
.attr('class','option-boro') | |
.text(d => d) | |
const nta = d3.select('.menu') | |
.selectAll('.select-nta') | |
.data([null]) | |
.enter().append('select') | |
.attr('class','select-nta') | |
.on('change', update) | |
nta.selectAll('option.option-nta') | |
.data(borosNtas[selectedBoro], d => d) | |
.enter().append('option') | |
.attr('class','option-nta') | |
.text(d => d) | |
const legend = d3.select('.menu') | |
.selectAll('.legend') | |
.data([null]) | |
.enter().append('div') | |
.attr('class','legend') | |
.selectAll('.legend-entry') | |
.data(healthStatuses) | |
.enter().append('div') | |
.attr('class','legend-entry') | |
.style('background', d => colorFn(d)) | |
.text(d => d) | |
.on('mouseenter', d => { | |
d3.select('.treemap').selectAll('.node:not(.'+d.toLowerCase()+')') | |
.transition() | |
.duration(250) | |
.style("background", "#bbb") | |
}) | |
.on('mouseleave', () => { | |
d3.select('.treemap').selectAll('.node') | |
.transition() | |
.duration(250) | |
.style("background", d => colorFn(d.data.key)) | |
}) | |
update(); | |
} | |
function update(){ | |
selectedBoro = d3.select('.select-boro').property('value'); | |
let optionsNta = d3.select('.select-nta') | |
.selectAll('option.option-nta') | |
.data(borosNtas[selectedBoro], d => d) | |
optionsNta.exit().remove(); | |
optionsNta.enter().append('option') | |
.attr('class','option-nta') | |
.text(d => d); | |
selectedNta = d3.select('.select-nta').property('value'); | |
let currentTree = nested[Object.keys(borosNtas).indexOf(selectedBoro)] | |
.values[borosNtas[selectedBoro].indexOf(selectedNta)]; | |
const root = d3.hierarchy(currentTree, d => d.values) | |
.sum(d => d.value) | |
.sort((a,b) => b.height - a.height || b.value - a.value ); | |
let node = d3.select('.treemap').datum(root).selectAll(".node") | |
.data(treemapFn(root).leaves(), d => d.parent.data.key+"_"+d.data.key); | |
node.exit() | |
.transition() | |
.duration(250) | |
.style('opacity',0) | |
.remove() | |
node = node.enter().append("div") | |
.attr("class", d => "node "+d.data.key) | |
.merge(node) | |
.transition() | |
.delay(250) | |
.duration(500) | |
.style("left", d => d.x0 + "px") | |
.style("top", d => d.y0 + "px") | |
.style("width", d => Math.max(0, d.x1 - d.x0) + "px") | |
.style("height", d => Math.max(0, d.y1 - d.y0) + "px") | |
.style("background", d => colorFn(d.data.key)) | |
.text(d => d === d.parent.children[0] ? d.parent.data.key : '') | |
.on("end", function(){ | |
d3.select(this) | |
.html(d => ` | |
<div>${d === d.parent.children[0] ? d.parent.data.key : ''}</div> | |
<div class="on-hover">${d.value}</div> | |
`) | |
}) | |
} | |
dataLoaded(); | |
}) | |
</script> | |
</html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment