Skip to content

Instantly share code, notes, and snippets.

@HarryStevens
Last active Sep 24, 2019
Embed
What would you like to do?
Treemap Update Pattern
license: gpl-3.0

Using D3's general update pattern to create a randomly updating treemap.

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<style>
body {
margin: 0;
font-family: "Helvetica Neue", sans-serif;
}
</style>
</head>
<body>
<script src="https://d3js.org/d3.v5.min.js"></script>
<script>
const margin = {top: 0, right: 0, bottom: 0, left: 0},
aspect = .85,
minHeight = 400,
duration = 1000,
categories = "abcdef".split(""),
colors = {};
categories.forEach((d, i) => {
colors[d] = d3.schemeSet2[i];
});
const treemap = d3.treemap()
.padding(1)
.round(true);
const svg = d3.select("body").append("svg");
const g = svg.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
let root = d3.hierarchy(makeData())
.sum(d => d.value)
.sort((a, b) => b.value - a.value);
draw();
onresize = _ => draw(true);
d3.interval(_ => {
root = d3.hierarchy(makeData())
.sum(d => d.value)
.sort((a, b) => b.value - a.value);
draw();
}, duration * 2);
function draw(resizing){
width = innerWidth - margin.left - margin.right;
let baseHeight = innerWidth * aspect;
baseHeight = baseHeight < minHeight ? minHeight : baseHeight > innerHeight ? innerHeight : baseHeight;
height = baseHeight - margin.top - margin.bottom;
svg
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom);
g
.attr("transform", `translate(${margin.left}, ${margin.top})`);
treemap.size([width, height]);
const leaves = treemap(root).leaves();
const rects = svg.selectAll(".rect")
.data(leaves, d => d.data.name);
if (resizing){
rects.exit().remove();
rects
.attr("transform", d => `translate(${d.x0},${d.y0})`)
.attr("width", d => d.x1 - d.x0)
.attr("height", d => d.y1 - d.y0);
} else {
rects.exit()
.style("opacity", 1)
.transition().duration(duration)
.style("opacity", 1e-6)
.remove();
rects.transition().duration(duration)
.attr("transform", d => `translate(${d.x0},${d.y0})`)
.attr("width", d => d.x1 - d.x0)
.attr("height", d => d.y1 - d.y0);
}
rects.enter().append("rect")
.attr("class", "rect")
.style("fill", d => colors[d.parent.data.name])
.attr("transform", d => `translate(${d.x0},${d.y0})`)
.attr("width", d => d.x1 - d.x0)
.attr("height", d => d.y1 - d.y0)
.style("opacity", 1e-6)
.transition().duration(duration)
.style("opacity", 1);
const labels = svg.selectAll(".label")
.data(leaves.filter(f => f.x1 - f.x0 > 60 && f.y1 - f.y0 > 30), d => d.data.name);
if (resizing){
labels.exit().remove();
labels
.html(d => `<tspan style='font-weight: 500'>${d.data.name}</tspan><tspan dx=10>${d.data.value}</tspan>`)
.attr("transform", d => `translate(${d.x0}, ${d.y0})`);
} else {
labels.exit()
.style("opacity", 1)
.transition().duration(duration)
.style("opacity", 1e-6)
.remove();
labels
.html(d => `<tspan style='font-weight: 500'>${d.data.name}</tspan><tspan dx=10>${d.data.value}</tspan>`)
.transition().duration(duration)
.attr("transform", d => `translate(${d.x0}, ${d.y0})`);
}
labels.enter().append("text")
.attr("class", "label")
.attr("dy", 16)
.attr("dx", 5)
.attr("transform", d => `translate(${d.x0}, ${d.y0})`)
.html(d => `<tspan style='font-weight: 500'>${d.data.name}</tspan><tspan dx=10>${d.data.value}</tspan>`)
.style("opacity", 1e-6)
.transition().duration(duration)
.style("opacity", 1);
}
function makeData(){
return {
name: "root",
children: categories.map(name => {
return {
name,
children: d3.range(randBetween(5, 10)).map((d, i) => {
return {
name: `${name}${i}`,
value: randBetween(10, 100)
}
})
}
})
}
}
function randBetween(min, max){
return Math.floor(Math.random() * (max - min + 1) + min);
}
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment