Skip to content

Instantly share code, notes, and snippets.

@jbbarre
Last active March 19, 2020 20:42
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 jbbarre/2187560809ca8ed6630d693e2e75edf4 to your computer and use it in GitHub Desktop.
Save jbbarre/2187560809ca8ed6630d693e2e75edf4 to your computer and use it in GitHub Desktop.
treemap
width: 820
height: 730
GRANT_COMMUNITY YEAR GRANT_AWARD
GB etranger 10523796
ES+PT etranger 10294805
Autres etranger 22228695
DK+SE+NO etranger 4785253
BE+LU etranger 8339353
CH etranger 2367618
US etranger 1395887
NL etranger 7581394
DE etranger 12121476
IT etranger 3352897
74 france 1302280
23 france 217227
5 france 262162
44 france 2448833
42 france 1749606
77 france 2565048
58 france 350308
85 france 1296360
13 france 4100682
91 france 2551076
46 france 766763
40 france 983320
45 france 1216651
18 france 566252
78 france 2597637
97 france 37743
71 france 917453
2A france 330591
41 france 545824
7 france 674119
92 france 3059305
61 france 415923
34 france 2507494
81 france 862538
99 france 171
69 france 4141822
15 france 425641
93 france 2810910
52 france 237994
72 france 884640
79 france 636069
8 france 327019
82 france 898176
59 france 3486515
62 france 1694985
27 france 918051
33 france 4276379
4 france 312591
57 france 1210033
43 france 576254
84 france 1179876
16 france 646658
55 france 249150
19 france 693132
68 france 758442
70 france 327970
47 france 1066307
48 france 337980
60 france 1149107
28 france 672275
36 france 358887
29 france 1177381
89 france 532141
26 france 1058941
80 france 663232
35 france 1532756
39 france 390251
86 france 755520
12 france 792289
88 france 449939
11 france 581438
49 france 1328316
38 france 2946022
3 france 685615
67 france 1129738
17 france 1189051
25 france 824428
66 france 1330005
14 france 928681
31 france 4532704
21 france 906274
95 france 2106720
64 france 1475114
65 france 527007
6 france 1553068
32 france 518742
76 france 1795063
24 france 922875
22 france 810488
51 france 818707
10 france 414753
9 france 419255
73 france 823750
50 france 592074
37 france 902443
83 france 1707641
2 france 608416
53 france 441094
98 france 43226
90 france 187041
56 france 1123680
94 france 2680112
87 france 841867
1 france 1185274
54 france 1116371
30 france 1305725
75 france 4575679
63 france 1929595
#chart {
width: 800px;
/* height: 620px; */
margin: 0 auto;
padding-top: 30px;
}
text {
pointer-events: none;
}
.grandparent text {
font-weight: bold;
}
rect {
stroke: #fff;
stroke-width: 1px;
}
rect.parent,
.grandparent rect {
stroke-width: 2px;
}
.children rect.parent,
.grandparent rect {
cursor: pointer;
}
.children rect.child {
opacity: 0;
}
.children:hover rect.child {
opacity: 1;
stroke-width: 1px;
}
.children:hover rect.parent {
opacity: 0;
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
<link rel="stylesheet" href="index.css">
</head>
<body>
<div id="chart"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.17/d3.min.js"></script>
<script src="index.js"></script>
</body>
</html>
var log = console.log;
var margin = {top: 30, right: 0, bottom: 0, left: 0};
var width = 800;
var height = 670 - margin.top - margin.bottom;
var transitioning;
// x axis scale
var x = d3.scale.linear()
.domain([0, width])
.range([0, width]);
// y axis scale
var y = d3.scale.linear()
.domain([0, height])
.range([0, height]);
// treemap layout
var treemap = d3.layout.treemap()
.children(function(d, depth) { return depth ? null : d._children; })
.sort(function(a, b) { return a.value - b.value; })
.ratio(height / width * 0.5 * (1 + Math.sqrt(5)))
.round(false);
// define svg
var svg = d3.select("#chart").append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.bottom + margin.top)
.style("margin-left", -margin.left + "px")
.style("margin.right", -margin.right + "px")
.append("g")
.attr("transform", `translate(${margin.left},${margin.top})`)
.style("shape-rendering", "crispEdges");
// top gray rectangle
var grandparent = svg.append("g")
.attr("class", "grandparent");
// Add grandparent rect
grandparent.append("rect")
.attr("y", -margin.top)
.attr("width", width)
.attr("height", margin.top)
.style("fill", "#d9d9d9");
// Add grandparent text
grandparent.append("text")
.attr("class", "title")
.attr("x", 6)
.attr("y", 6 - margin.top)
.attr("dy", ".75em");
// custom root initializer
function initialize(root) {
root.x = root.y = 0;
root.dx = width;
root.dy = height;
root.depth = 0;
};
// Aggregate the values for internal nodes. This is normally done by the
// treemap layout, but not here because of our custom implementation.
// We also take a snapshot of the original children (_children) to avoid
// the children being overwritten when when layout is computed.
// Alteration made for function to be iterative
function accumulateVal(d, attr) {
return accumulate(d);
function accumulate(d) {
return (d._children = d.children)
// recursion step, note that p and v are defined by reduce
? d[attr] = d.children.reduce(function(p, v) {return p + accumulate(v); }, 0)
: d[attr];
};
};
// Compute the treemap layout recursively such that each group of siblings
// uses the same size (1×1) rather than the dimensions of the parent cell.
// This optimizes the layout for the current zoom state. Note that a wrapper
// object is created for the parent node for each group of siblings so that
// the parent’s dimensions are not discarded as we recurse. Since each group
// of sibling was laid out in 1×1, we must rescale to fit using absolute
// coordinates. This lets us use a viewport to zoom.
function layout(d) {
if (d._children) {
// treemap nodes comes from the treemap set of functions as part of d3
treemap.nodes({_children: d._children});
d._children.forEach(function(c) {
c.x = d.x + c.x * d.dx;
c.y = d.y + c.y * d.dy;
c.dx *= d.dx;
c.dy *= d.dy;
c.parent = d;
// recursion
layout(c);
});
}
};
d3.csv("data_treemap.csv", treeMapZoomable);
function treeMapZoomable(error, grants) {
if (error) throw error;
// Define color scale
var allValues = grants.map(function(d) { return [d["GRANT_PROGRAM"], d["YEAR"]]; })
.reduce(function(acc, curVal) { return acc.concat(curVal); }, []);
var scaleOrdNames = [...new Set(allValues)];
var colorScale = d3.scale.ordinal().domain(scaleOrdNames)
.range(['#66c2a5','#fc8d62','#8da0cb','#e78ac3','#a6d854','#ffd92f','#e5c494']);
// Define aggregrated grant programs
var aggGrantPrograms = d3.nest().key(function(d) { return d.YEAR; })
.key(function(d) { return d["GRANT_PROGRAM"]; })
.entries(grants)
.map(function(d) {
var dValues = d.values.map(function(g){
var gValues = g.values;
var sum = gValues.map(function(p) { return p["GRANT_AWARD"]; })
.reduce(function(acc, curVal) { return acc + (+curVal); }, 0);
return {
name: g.key,
value: gValues.length,
sum: sum
};
});
return {
name: d.key,
children: dValues
};
});
// Root for hierarchy
var rootObject = {name: "Public Grants", children: aggGrantPrograms};
initialize(rootObject);
["value", "sum"].forEach(function(d) { accumulateVal(rootObject, d); });
layout(rootObject);
display(rootObject);
function display(d) {
// Grandparent when clicked transitions the children out to parent
grandparent
.datum(d.parent)
.on("click", transition)
.select("text.title")
.text(name(d));
grandparent
.datum(d.parent)
.select("rect");
var g1 = svg.insert("g", ".grandparent")
.datum(d)
.attr("class", "depth");
var g = g1.selectAll("g")
.data(d._children)
.enter().append("g");
// When parent is clicked transitions to children
g.filter(function(d) { return d._children; })
.classed("children", true)
.on("click", transition);
g.selectAll(".child")
.data(function(d) { return d._children || [d]; })
.enter().append("rect")
.attr("class", "child")
.call(rect);
g.append("rect")
.attr("class", "parent")
.call(rect)
.append("title")
.text(function(d) { return `Grant Program: ${d.name}`; });
// Appending year, grants, and sum texts for each g
var textClassArr = [
{class: "year", accsor: function(d) { return d.name; }},
{class: "grants", accsor: function(d) { return `${d.value} grants`; }},
{class: "sum", accsor: function(d) { return `$${d.sum.toLocaleString()}`; }}
];
textClassArr.forEach(function(p, i) {
g.append("text")
.attr("class", p.class)
.attr("dy", ".5em")
.text(p.accsor)
.call(rectText(i));
});
function transition(d) {
if (transitioning || !d) return;
transitioning = true;
var g2 = display(d);
var t1 = g1.transition().duration(750);
var t2 = g2.transition().duration(750);
// Update the domain only after entering new elements.
x.domain([d.x, d.x + d.dx]);
y.domain([d.y, d.y + d.dy]);
// Enable anti-aliasing during the transition.
svg.style("shape-rendering", null);
// Draw child nodes on top of parent nodes.
svg.selectAll(".depth").sort(function(a, b) { return a.depth - b.depth; });
// Fade-in entering text.
g2.selectAll("text").style("fill-opacity", 0);
// Transition to the new view.
textClassArr.forEach(function(d, i) {
var textClass = `text.${d.class}`;
var textPlacmt = rectText(i);
t1.selectAll(textClass).call(textPlacmt).style("fill-opacity", 0);
t2.selectAll(textClass).call(textPlacmt).style("fill-opacity", 1);
});
t1.selectAll("rect").call(rect);
t2.selectAll("rect").call(rect);
// Remove the old node when the transition is finished.
t1.remove().each("end", function() {
svg.style("shape-rendering", "crispEdges");
transitioning = false;
});
};
return g;
};
function rectText(i) {
return grantText;
function grantText(text) {
return text.attr("x", function(d) { return x(d.x) + 6; })
.attr("y", function(d) { return y(d.y) + (25 + (i * 18)); })
.attr("fill", "black");
};
};
function rect(rect) {
rect.attr("x", function(d) { return x(d.x); })
.attr("y", function(d) { return y(d.y); })
.attr("width", function(d) { return x(d.x + d.dx) - x(d.x); })
.attr("height", function(d) { return y(d.y + d.dy) - y(d.y); })
.attr("fill", function(d) { return colorScale(d.name);});
};
function name(d) {
return d.parent
? `${name(d.parent)}.${d.name} - Total Grants: ${d.value} -
Total Award: $${d.sum.toLocaleString()}`
: d.name;
};
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment