Skip to content

Instantly share code, notes, and snippets.

@ckuijjer
Created August 31, 2017 10:58
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 ckuijjer/53647c446801932b173c043338b86b75 to your computer and use it in GitHub Desktop.
Save ckuijjer/53647c446801932b173c043338b86b75 to your computer and use it in GitHub Desktop.
Revised treemap from R to Illustrator
license: mit
id,views,comments,category
5019,148896,28,Artistic Visualization
1416,81374,26,Visualization
1416,81374,26,Featured
3485,80819,37,Featured
3485,80819,37,Mapping
3485,80819,37,Data Sources
500,76495,10,Statistical Visualization
500,76495,10,Mapping
500,76495,10,Network Visualization
4092,66650,70,Ugly Visualization
4092,66650,70,Mistaken Data
2432,42512,17,Infographics
4449,36166,10,Featured
4449,36166,10,Visualization
836,34972,56,Projects
836,34972,56,Featured
836,34972,56,Mapping
836,34972,56,Data Sources
4869,33705,90,Infographics
3100,32143,45,Data Sources
3100,32143,45,Featured
4122,31076,8,Mapping
1079,29646,41,Software
1079,29646,41,Featured
975,25247,11,Self-surveillance
2545,25190,12,Miscellaneous Visualization
979,25062,25,Featured
979,25062,25,Self-surveillance
540,24302,8,Featured
540,24302,8,Visualization
3581,23311,87,Mapping
3581,23311,87,Tutorials
3581,23311,87,Featured
1551,21632,22,Mapping
1245,20746,14,Featured
1245,20746,14,Visualization
889,20703,26,Featured
889,20703,26,Projects
889,20703,26,Statistical Visualization
763,20446,5,Featured
763,20446,5,Visualization
1141,20016,37,Infographics
4627,19763,11,Featured
4627,19763,11,Data Design Tips
1585,19640,25,Featured
1585,19640,25,Data Design Tips
1585,19640,25,Statistics
409,19266,12,Statistical Visualization
4976,18950,7,Miscellaneous Visualization
663,18887,24,Infographics
1458,18839,13,Infographics
2525,18697,2,Network Visualization
800,18612,25,Quotes
498,18182,5,Data Sources
681,16850,13,Miscellaneous
2591,16669,10,Infographics
949,16292,2,Miscellaneous
1541,15914,45,Visualization
1541,15914,45,Statistics
1541,15914,45,Featured
459,15752,14,Infographics
655,15599,10,Featured
655,15599,10,Software
3805,15536,42,Infographics
7,15260,2,Visualization
4075,15138,58,Mapping
3935,15104,14,Data Design Tips
3935,15104,14,Featured
2551,14846,39,Data Design Tips
2551,14846,39,Featured
706,14123,16,Statistics
4276,13670,14,Miscellaneous Visualization
1223,12560,7,Statistical Visualization
1223,12560,7,Data Design Tips
637,12341,13,Visualization
842,12155,4,Mapping
842,12155,4,Economics
364,12012,9,Featured
3544,11948,6,Visualization
645,11790,10,Data Sources
517,11709,1,Artistic Visualization
1303,11651,8,Infographics
682,11266,2,Artistic Visualization
2711,11230,2,Artistic Visualization
3228,11081,8,Mapping
1062,10820,7,Mapping
4884,10810,37,Statistical Visualization
4884,10810,37,Tutorials
4884,10810,37,Featured
952,10748,12,Mapping
952,10748,12,Projects
952,10748,12,Featured
1343,10746,18,Statistical Visualization
717,10649,6,Visualization
1293,10460,3,Mapping
3459,10286,6,Infographics
2441,10114,12,Miscellaneous Visualization
1016,9954,9,Miscellaneous
2734,9086,8,Statistical Visualization
687,8993,0,Infographics

An implementation in D3 of Figure 5-19 "Revised treemap from R to Illustrator" in "Visualize This" by Nathan Yau.

The color scale looks way more washed out than the one in the book unfortunately

const width = 500,
height = 400,
margin = { top: 0, right: 0, bottom: 0, left: 0 },
chartWidth = width - margin.left - margin.right,
chartHeight = height - margin.top - margin.bottom;
const colorScale = d3
.scaleLinear()
.interpolate(d3.interpolateRgb.gamma(2.2))
.range([d3.rgb(0, 0, 0), d3.rgb(115, 192, 60)]);
const treemap = d3
.treemap()
.tile(d3.treemapSquarify)
.size([chartWidth, chartHeight])
.round(true)
.paddingOuter(d => (d.depth === 0 ? 0 : 1));
const svg = d3
.select('#container')
.append('svg')
.attr('width', width)
.attr('height', height);
const chart = svg
.append('g')
.attr('transform', `translate(${margin.left}, ${margin.top})`);
const getStartAndEndOfEachWord = str => {
const regex = /\w+/g;
const result = [];
let match;
while ((match = regex.exec(str))) {
const start = match.index;
const end = start + match[0].length;
result.push([start, end]);
}
return result;
};
// horrible implementation of https://en.wikipedia.org/wiki/Composition_(combinatorics)
const getCompositions = list => {
const bits = list.length - 1;
const number_of_compositions = Math.pow(2, bits);
return new Array(number_of_compositions).fill(0).map((_, i) => {
const composition = i.toString(2).padStart(bits, '0').split('').reduce((
acc,
bit,
j
) => {
const item = list[j + 1];
if (item) {
if (bit === '0') {
// add it to the last list
acc[acc.length - 1].push(item);
} else {
// add it as a new list
acc.push([item]);
}
}
return acc;
}, [[list[0]]]);
return composition;
});
};
d3.csv('.post-data.txt', (err, data) => {
colorScale.domain(d3.extent(data, d => d.comments));
const nest = d3.nest().key(d => d.category);
const nestedData = nest.entries(data);
const root = d3
.hierarchy({ values: nestedData }, d => d.values)
.sum(d => d.views)
.sort((a, b) => b.views - a.views);
treemap(root);
const category = chart
.selectAll('.category')
.data(root.children)
.enter()
.append('g')
.attr('class', 'category');
category.append('title').text(d => d.data.key);
const post = category
.selectAll('.post')
.data(d => d.children)
.enter()
.append('rect')
.attr('class', 'post')
.attr('x', d => d.x0)
.attr('y', d => d.y0)
.attr('width', d => d.x1 - d.x0)
.attr('height', d => d.y1 - d.y0)
.attr('fill', d => colorScale(d.data.comments));
const categoryText = category
.append('g')
.attr('width', d => d.x1 - d.x0)
.attr('height', d => d.y1 - d.y0)
.attr('transform', d => `translate(${d.x0}, ${d.y0})`)
.append('text')
.attr('class', 'category-text')
.text(d => d.data.key)
.attr('dy', '1em');
// use the temporary text that was just rendered to calculate, wrap and resize the text parts
categoryText
.selectAll('tspan')
.data((d, i, nodes) => {
const text = d.data.key;
const textNode = nodes[i];
// find the indices where words are, e.g. "hi you" => [[0, 1], [3, 5]]
const indices = getStartAndEndOfEachWord(text);
// create all possible ways this can be split into lines, e.g. [[[[[0, 1]], [[3, 5]]], [[0, 1], [3, 5]]]
const compositions = getCompositions(indices);
const textHeight = textNode.getBBox().height;
const PADDING = 4;
const containerWidth = d.x1 - d.x0 - PADDING * 2;
const containerHeight = d.y1 - d.y0 - PADDING * 2;
compositions.forEach(lines => {
lines.forEach(words => {
const startOfFirstWord = words[0][0];
const endOfLastWord = words[words.length - 1][1];
const numberOfCharacters = endOfLastWord - startOfFirstWord;
const width = textNode.getSubStringLength(
startOfFirstWord,
numberOfCharacters
);
words.width = width;
words.maximumHorizontalZoom = containerWidth / Math.ceil(width);
});
const height = textHeight * lines.length;
lines.height = height;
lines.maximumVerticalZoom = containerHeight / Math.ceil(height);
lines.maximumHorizontalZoom = Math.min(
...lines.map(x => x.maximumHorizontalZoom)
);
lines.maximumZoom = Math.min(
lines.maximumVerticalZoom,
lines.maximumHorizontalZoom
);
});
compositions.sort((a, b) => b.maximumZoom - a.maximumZoom);
const best = compositions[0];
// directly set the zoom to the textNode
d3
.select(textNode)
.html('') // dirty trick to clear the children
.attr(
'transform',
`translate(${PADDING},${PADDING}), scale(${best.maximumZoom}, ${best.maximumZoom})`
);
return best.map(words => {
const startOfFirstWord = words[0][0];
const endOfLastWord = words[words.length - 1][1];
return text.substring(startOfFirstWord, endOfLastWord);
});
})
.enter()
.append('tspan')
.attr('x', 0)
.attr('dy', '1em')
.text(d => d);
});
<!DOCTYPE html>
<body>
<style>
body {
width: 100%;
font-family: Georgia;
font-size: 14px;
color: #333;
}
#container {
width: 500px;
margin: 0 auto;
position: relative;
}
#header h1 {
text-transform: uppercase;
font-size: 18px;
}
#header {
line-height: 1.4em;
margin-bottom: 16px;
}
.category-text {
font-family: Arial;
fill: #fff;
}
</style>
<div id="container">
<div id="header">
<h1>FlowingData map</h1>
Below are popular posts on FlowingData. Each rectangle represents a post. Size represents number of views and brigther green indicates more comments
</div>
</div>
<script src="https://d3js.org/d3.v4.min.js"></script>
<script src="app.js"></script>
</body>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment