Skip to content

Instantly share code, notes, and snippets.

@anbnyc
Created February 1, 2018 16:27
Show Gist options
  • Save anbnyc/a0b946d3d2dde50b424ab9904e9645cd to your computer and use it in GitHub Desktop.
Save anbnyc/a0b946d3d2dde50b424ab9904e9645cd to your computer and use it in GitHub Desktop.
Treemap based on NYC 2015 Tree Census
<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