A treemap as a space-filling cartogram built with D3.js. Inspired by work by Jo Wood and Jason Dykes. Data are from the US Census Bureau API.
Last active
May 21, 2019 14:41
-
-
Save gmculp/2cc6cc0b81bcd18669d1 to your computer and use it in GitHub Desktop.
Spatially Ordered 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
<!DOCTYPE html> | |
<meta charset="utf-8"> | |
<style> | |
body { | |
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; | |
margin: auto; | |
position: relative; | |
width: 90%; | |
} | |
form { | |
position: absolute; | |
right: 10px; | |
top: 10px; | |
} | |
.node { | |
border: solid 1px white; | |
font: 10px sans-serif; | |
line-height: 12px; | |
overflow: hidden; | |
position: absolute; | |
text-indent: 2px; | |
} | |
</style> | |
<form> | |
<label><input type="radio" name="mode" value="size"> Land Area</label> | |
<label><input type="radio" name="mode" value="Population" checked> Total Population</label> | |
<label><input type="radio" name="mode" value="Minors"> Minor Population</label> | |
<label><input type="radio" name="mode" value="Seniors"> Senior Population</label> | |
<!--<label><input type="radio" name="mode" value="Females"> Female Population</label> | |
<label><input type="radio" name="mode" value="Males"> Male Population</label>--> | |
</form> | |
<!--[if lte IE 8]><script src="http://cdnjs.cloudflare.com/ajax/libs/r2d3/0.2.0/r2d3.min.js"></script><![endif]--> | |
<!--[if gte IE 9]><!--> | |
<script type="text/javascript" src="http://d3js.org/d3.v3.js"></script> | |
<!--<![endif]--> | |
<script src="http://code.jquery.com/jquery-1.9.1.js"></script> | |
<script type="text/javascript"> | |
var census_var = [ | |
{name:"P001001",label:"Total Population",type:"Population"}, | |
<!--{name:"P0120002",label:"Male:",type:"Males"},--> | |
{name:"P012003",label:"Male: !! Under 5 years",type:"Minors"}, | |
{name:"P012004",label:"Male: !! 5 to 9 years",type:"Minors"}, | |
{name:"P012005",label:"Male: !! 10 to 14 years",type:"Minors"}, | |
{name:"P012006",label:"Male: !! 15 to 17 years",type:"Minors"}, | |
{name:"P012020",label:"Male: !! 65 and 66 years",type:"Seniors"}, | |
{name:"P012021",label:"Male: !! 67 and 69 years",type:"Seniors"}, | |
{name:"P012022",label:"Male: !! 70 and 74 years",type:"Seniors"}, | |
{name:"P012023",label:"Male: !! 75 and 79 years",type:"Seniors"}, | |
{name:"P012024",label:"Male: !! 80 and 84 years",type:"Seniors"}, | |
{name:"P012025",label:"Male: !! 85 years and over",type:"Seniors"}, | |
<!--{name:"P0120026",label:"Female:",type:"Females"},--> | |
{name:"P012027",label:"Female: !! Under 5 years",type:"Minors"}, | |
{name:"P012028",label:"Female: !! 5 to 9 years",type:"Minors"}, | |
{name:"P012029",label:"Female: !! 10 to 14 years",type:"Minors"}, | |
{name:"P012030",label:"Female: !! 15 to 17 years",type:"Minors"}, | |
{name:"P012044",label:"Female: !! 65 and 66 years",type:"Seniors"}, | |
{name:"P012045",label:"Female: !! 67 and 69 years",type:"Seniors"}, | |
{name:"P012046",label:"Female: !! 70 and 74 years",type:"Seniors"}, | |
{name:"P012047",label:"Female: !! 75 and 79 years",type:"Seniors"}, | |
{name:"P012048",label:"Female: !! 80 and 84 years",type:"Seniors"}, | |
{name:"P012049",label:"Female: !! 85 years and over",type:"Seniors"} | |
]; | |
//var census_path = "https://api.census.gov/data/2010/sf1?key=c8b757f7e16f108647304131056db5bb63ba2e93&get="; | |
var census_path = "https://api.census.gov/data/2010/dec/sf1?key=c8b757f7e16f108647304131056db5bb63ba2e93&get="; | |
for (i = 0; i < census_var.length; ++i) { | |
census_path += (i==(census_var.length-1))?census_var[i].name:census_var[i].name+","; | |
} | |
census_path += "&for=state:*"; | |
var tree_path = "states_tree.json"; | |
var win_w = $( window ).width()*0.9; | |
var win_h = $( window ).height()*0.9; | |
var margin = {top: 60, right: 10, bottom: 10, left: 10}, | |
width = win_w - margin.left - margin.right, | |
height = win_h - margin.top - margin.bottom; | |
var l_width = 200 - margin.left; | |
var l_height = margin.top - (margin.left*2); | |
var values = d3.range(l_width); | |
var color2 = d3.scale.linear() | |
.range(["rgb(0,155,255)","rgb(150,150,150)","rgb(255,100,0)"]) | |
.interpolate(d3.interpolateRgb) | |
.domain([0, (values.length - 1)/2, values.length - 1]); | |
var x = d3.scale.ordinal() | |
.domain(values) | |
.rangeRoundBands([0, l_width]); | |
var svg = d3.select("body").append("svg") | |
.attr("width", l_width+margin.left) | |
.attr("height", l_height+(margin.left*2)); | |
var g = svg.append("g").attr("transform", function(d) { return "translate(" + margin.left + "," + margin.left + ")"; }); | |
var r = g.selectAll("rect") | |
.data(values) | |
.enter().append("rect") | |
.attr("x", x) | |
.attr("y",l_height/2) | |
.attr("width", x.rangeBand()) | |
.attr("height", l_height/2) | |
.style("fill", color2); | |
g.append("line") | |
.attr("x1", l_width/2) | |
.attr("x2", l_width/2) | |
.attr("y1", 0) | |
.attr("y2", l_height) | |
.style("stroke","black") | |
.style("stroke-width","2"); | |
g.append("text") | |
.attr("x", 0) | |
.attr("y", l_height/ 2) | |
.attr("dy", "-0.35em") | |
.attr("text-anchor", "start") | |
.text("contract"); | |
g.append("text") | |
.attr("x", l_width) | |
.attr("y", l_height/ 2) | |
.attr("dy", "-0.35em") | |
.attr("text-anchor", "end") | |
.text("expand"); | |
var treemap = d3.layout.treemap() | |
.size([width, height]) | |
.sticky(true) | |
.mode("slice-dice") | |
.sort(function(d) { return d.order; }); //order by JSON field | |
var div = d3.select("body").append("div") | |
.attr("id", "chart") | |
.attr("class", "chart") | |
.style("position", "absolute") | |
.style("width", (width + margin.left + margin.right) + "px") | |
.style("height", (height + margin.top + margin.bottom) + "px") | |
.style("left", margin.left + "px") | |
.style("top", margin.top + "px"); | |
$.getJSON(census_path, function(c_json) { | |
//grab variable names | |
var c_var = c_json.shift(); | |
//assign variable names as keys | |
var cdata2=[]; | |
for (i = 0; i < c_json.length; ++i) { | |
var c = c_json[i]; | |
var temp_OL = {}; | |
for (j = 0; j < c.length; ++j) { | |
temp_OL[c_var[j]] = c[j]; | |
} | |
cdata2.push(temp_OL); | |
} | |
//get type for each variable name | |
var type_OL = {}; | |
for (i = 0; i < census_var.length; ++i) { | |
type_OL[census_var[i].name] =census_var[i].type; | |
} | |
//create object for each state with variables summed by type | |
var cdata={}; | |
for (i = 0; i < cdata2.length; ++i) { | |
var temp_OL = {}; | |
var p = cdata2[i]; | |
for (var key in p) { | |
if (p.hasOwnProperty(key)) { | |
if (type_OL.hasOwnProperty(key)) { | |
if (temp_OL.hasOwnProperty(type_OL[key])) { | |
temp_OL[type_OL[key]] = temp_OL[type_OL[key]] + parseInt(p[key]); | |
} | |
else { | |
temp_OL[type_OL[key]] =parseInt(p[key]); | |
} | |
} | |
} | |
} | |
cdata[p["state"]]=temp_OL; | |
} | |
$.getJSON(tree_path, function(root) { | |
var this_tree = treemap.value(function(d) { return d.size; }).nodes(root); | |
this_tree.forEach(function(d) { d.real_size=d.dx * d.dy;}); | |
var node = div.selectAll(".node") | |
.data(this_tree) | |
.enter().append("div") | |
.attr("class", "node") | |
.attr("id", function(d){ return d.id;}) | |
.call(position) | |
.text(function(d) { return d.children ? null : d.name; }); | |
$( "input" ).change(function () { | |
var radio_value = $("input[name='mode']:checked").val(); | |
var value = (radio_value === "size") | |
? function(d) { return d.size; } //return square feet from JSON | |
: function(d) { return cdata[d.id][radio_value]; }; //return census data from Census API | |
//update color scale domain based on selected variable | |
if (radio_value === "size") { | |
color2.domain([0, 1, 2]); | |
} | |
else { | |
var that_tree = treemap.value(value).nodes(root).map(function(d) { return (d.dx * d.dy)/d.real_size; }); | |
color2.domain([d3.min(that_tree), 1, d3.max(that_tree)]); | |
} | |
node.data(treemap.value(value).nodes) | |
.transition() | |
.duration(1500) | |
.call(position); | |
}).change(); | |
}); | |
}); | |
function position() { | |
this.style("left", function(d) { return d.x + "px"; }) | |
.style("top", function(d) { return d.y + "px"; }) | |
.style("width", function(d) { return Math.max(0, d.dx - 1) + "px"; }) | |
.style("height", function(d) { return Math.max(0, d.dy - 1) + "px"; }) | |
.style("background",function(d) { return color2((d.dx * d.dy)/d.real_size);}); | |
} | |
</script> |
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
{ | |
"name": "states_tree", | |
"children": [ | |
{ | |
"name": "col1", "order": 1, | |
"children": [ | |
{"name": "AK", "id":"05", "size": 576594, "order": 1, "hue": 3}, | |
{"name": "HI", "id":"15", "size": 6381, "order": 2, "hue": 1} | |
] | |
}, | |
{ | |
"name": "col2", "order": 2, | |
"children": [ | |
{"name": "WA", "id":"53", "size": 67290, "order": 1, "hue": 1}, | |
{"name": "OR", "id":"41", "size": 97074, "order": 2, "hue": 4}, | |
{"name": "CA", "id":"06", "size": 157776, "order": 3, "hue": 2} | |
] | |
}, | |
{"name": "col3", "order": 3, | |
"children": [ | |
{"name": "col3a", "order": 1, | |
"children": [ | |
{"name": "MT", "id":"30", "size": 147245, "order": 1, "hue": 2} | |
]}, | |
{"name": "col3b", "order": 2, | |
"children": [ | |
{"name": "ID", "id":"16", "size": 83344, "order": 1, "hue": 3}, | |
{"name": "WY", "id":"56", "size": 97803, "order": 2, "hue": 1} | |
]}, | |
{"name": "col3c", "order": 3, | |
"children": [ | |
{"name": "NV", "id":"32", "size": 110670, "order": 1, "hue": 1}, | |
{"name": "UT", "id":"49", "size": 84872, "order": 2, "hue": 2}, | |
{"name": "CO", "id":"08", "size": 104101, "order": 3, "hue": 3} | |
]}, | |
{"name": "col3d", "order": 4, | |
"children": [ | |
{"name": "AZ", "id":"04", "size": 113713, "order": 1, "hue": 3}, | |
{"name": "NM", "id":"35", "size": 121757, "order": 1, "hue": 1} | |
]} | |
]}, | |
{ | |
"name": "col4", "order": 4, | |
"children": [ | |
{"name": "ND", "id":"38", "size": 70812, "order": 1, "hue": 1}, | |
{"name": "SD", "id":"46", "size": 77195, "order": 2, "hue": 3}, | |
{"name": "NE", "id":"31", "size": 77330, "order": 3, "hue": 2}, | |
{"name": "KS", "id":"20", "size": 82197, "order": 4, "hue": 1}, | |
{"name": "OK", "id":"40", "size": 70003, "order": 5, "hue": 2}, | |
{"name": "TX", "id":"48", "size": 264436, "order": 6, "hue": 3} | |
] | |
}, | |
{ | |
"name": "col5", "order": 5, | |
"children": [ | |
{"name": "MN", "id":"30", "size": 84520, "order": 1, "hue": 2}, | |
{"name": "IA", "id":"19", "size": 56258, "order": 2, "hue": 1}, | |
{"name": "MO", "id":"29", "size": 69833, "order": 3, "hue": 3}, | |
{"name": "AR", "id":"04", "size": 52913, "order": 4, "hue": 1}, | |
{"name": "LA", "id":"22", "size": 45836, "order": 5, "hue": 2} | |
] | |
}, | |
{"name": "col7", "order": 6, | |
"children": [ | |
{"name": "col7a", "order": 1, | |
"children": [ | |
{"name": "WI", "id":"55", "size": 56088, "order": 1, "hue": 3}, | |
{"name": "MI", "id":"26", "size": 84520, "order": 2, "hue": 1} | |
]}, | |
{"name": "col7b", "order": 2, | |
"children": [ | |
{"name": "IL", "id":"17", "size": 56299, "order": 1, "hue": 2}, | |
{"name": "IN", "id":"18", "size": 36400, "order": 2, "hue": 4}, | |
{"name": "OH", "id":"39", "size": 41194, "order": 3, "hue": 3} | |
]}, | |
{"name": "col7c", "order": 3, | |
"children": [ | |
{"name": "KY", "id":"21", "size": 40320, "order": 1, "hue": 1} | |
]}, | |
{"name": "col7d", "order": 4, | |
"children": [ | |
{"name": "TN", "id":"47", "size": 42092, "order": 1, "hue": 2} | |
]}, | |
{"name": "col7e", "order": 5, | |
"children": [ | |
{"name": "MS", "id":"28", "size": 47619, "order": 1, "hue": 3}, | |
{"name": "AL", "id":"01", "size": 51716, "order": 2, "hue": 1} | |
]} | |
]}, | |
{"name": "col6", "order": 7, | |
"children": [ | |
{"name": "col6a", "order": 1, | |
"children": [ | |
{"name": "VT", "id":"50", "size": 9603, "order": 1, "hue": 1}, | |
{"name": "NH", "id":"33", "size": 9260, "order": 2, "hue": 2}, | |
{"name": "ME", "id":"23", "size": 32162, "order": 2, "hue": 1} | |
]}, | |
{"name": "col6b", "order": 2, | |
"children": [ | |
{"name": "MA", "id":"25", "size": 8173, "order": 1, "hue": 3} | |
]}, | |
{"name": "col6c", "order": 3, | |
"children": [ | |
{"name": "CT", "id":"09", "size": 4977, "order": 1, "hue": 1}, | |
{"name": "RI", "id":"44", "size": 1045, "order": 2, "hue": 4} | |
]}, | |
{"name": "col6d", "order": 2, | |
"children": [ | |
{"name": "NY", "id":"36", "size": 48562, "order": 1, "hue": 2} | |
]}, | |
{"name": "col6e", "order": 3, | |
"children": [ | |
{"name": "PA", "id":"42", "size": 45360, "order": 1, "hue": 4}, | |
{"name": "NJ", "id":"34", "size": 7508, "order": 2, "hue": 1} | |
]}, | |
{"name": "col6f", "order": 4, | |
"children": [ | |
{"name": "WV", "id":"54", "size": 24229, "order": 1, "hue": 2}, | |
{"name": "MD", "id":"24", "size": 9740, "order": 2, "hue": 1}, | |
{"name": "DE", "id":"10", "size": 2055, "order": 2, "hue": 2} | |
]}, | |
{"name": "col6g", "order": 5, | |
"children": [ | |
{"name": "VA", "id":"51", "size": 39820, "order": 1, "hue": 3} | |
]}, | |
{"name": "col6g", "order": 6, | |
"children": [ | |
{"name": "NC", "id":"37", "size": 49048, "order": 1, "hue": 1} | |
]}, | |
{"name": "col6h", "order": 7, | |
"children": [ | |
{"name": "GA", "id":"13", "size": 58629, "order": 1, "hue": 3}, | |
{"name": "SC", "id":"45", "size": 30867, "order": 2, "hue": 2} | |
]}, | |
{"name": "col6i", "order": 8, | |
"children": [ | |
{"name": "FL", "id":"12", "size": 55815, "order": 1, "hue": 4} | |
]} | |
] | |
} | |
] | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment