Published
Edited
Dec 1, 2020
1 fork
Importers
Insert cell
md`# Treemap`
Insert cell
d3 = require('d3')
Insert cell
import { range, sum } from '@nuuuwan/list-utils'
Insert cell
import { addDefaults } from '@nuuuwan/option-utils'
Insert cell
import {
getSVG,
drawRect,
drawText,
drawCircle,
drawMultiText
} from '@nuuuwan/svg-utils'
Insert cell
import { getRandomColor } from '@nuuuwan/color-utils'
Insert cell
function drawTreeMap(svg, data, options = {}) {
options = addDefaults(options, {
width: 1600,
height: 900,
rRounded: 32,
padding: 32,
funcDataToColor: function(data, i) {
return d3.schemeCategory10[i % 10];
},
funcDrawUnit: function(svg, [x0, y0], [itemWidth, itemHeight], d, i, j) {
const r = Math.min(itemWidth, itemHeight) / 2;
const [cx, cy] = [x0 + r, y0 + r];
drawCircle(svg, [cx, cy], r, { stroke: 'black' });
drawText(svg, [cx, cy], `${i}.${j}`, { fill: 'black', fontSize: r / 2 });
},
minFontSize: 16,
maxFontSize: 32,
maxCharsPerLine: 12
});

function valueIgnoringOther(d) {
if (d.name === 'Other') {
return 0;
}
return d.value;
}

const treeMapData = d3
.treemap()
.padding(options.padding)
.round(true)
.size([options.width, options.height])(
d3
.hierarchy(data)
.sum(d => d.value)
.sort((a, b) => valueIgnoringOther(b) - valueIgnoringOther(a))
)
.leaves();

treeMapData.forEach(function(d, i) {
const color = options.funcDataToColor(d.data, i);

const [rectWidth, rectHeight] = [d.x1 - d.x0, d.y1 - d.y0];
const text = `${d.data.name}(${d.data.value})`;
// const text = `${d.data.name}`;
const pTitleWidth = 0.6;
const isMultiLine = text.length > options.maxCharsPerLine;
const effectiveTextLength = isMultiLine
? text.length
: options.maxCharsPerLine;
const fontSize = Math.min(
options.maxFontSize,
Math.max(
options.minFontSize,
(rectWidth / effectiveTextLength) * pTitleWidth
)
);
const [titleWidth, titleHeight] = [
fontSize * effectiveTextLength * pTitleWidth * 0.8,
fontSize
];
const strokeWidth = Math.max(2, Math.min(4, fontSize / 4));

drawRect(svg, [d.x0, d.y0], [rectWidth, rectHeight], {
stroke: color,
strokeWidth: strokeWidth,
fill: 'white',
rx: options.rRounded,
ry: options.rRounded
});

const dataCount = d.data.value;
const availableRectHeight = rectHeight - fontSize / 2;
const area = rectWidth * availableRectHeight;
const k = Math.sqrt(dataCount / area);

const nx = Math.ceil(rectWidth * k);
const ny = Math.ceil(dataCount / nx);

const [itemWidth, itemHeight] = [rectWidth / nx, availableRectHeight / ny];

range(0, ny).forEach(function(iy) {
const y0 = d.y0 + iy * itemHeight + fontSize / 2;
const padding =
dataCount - iy * nx < nx ? (nx - dataCount + iy * nx) / 2 : 0;
range(0, nx).forEach(function(ix) {
const j = iy * nx + ix;
const x0 = d.x0 + (ix + padding) * itemWidth + padding;
if (j < dataCount) {
options.funcDrawUnit(svg, [x0, y0], [itemHeight, itemWidth], d, i, j);
}
});
});

drawRect(
svg,
[d.x0 + rectWidth / 2 - titleWidth / 2, d.y0 - titleHeight / 2],
[titleWidth, titleHeight],
{
stroke: 'none',
fill: 'white',
rx: options.rRounded,
ry: options.rRounded
}
);

const funcDrawText = isMultiLine ? drawMultiText : drawText;
funcDrawText(svg, [(d.x0 + d.x1) / 2, d.y0], text, {
fill: color,
fontSize: fontSize,
alignmentBaseline: 'middle',
maxCharsPerLine: options.maxCharsPerLine
});
});
}
Insert cell
TEST_DATA = Object({
name: 'Sri Lanka',
children: [
Object({ name: 'Colombo', value: 14, children: [] }),
Object({ name: 'Gampaha', value: 6, children: [] }),
Object({ name: 'Kalutara', value: 5, children: [] }),
Object({
name: 'Kalutara and other areas in the area',
value: 5,
children: []
}),
Object({ name: 'Others', value: 5, children: [] }),
Object({ name: 'Kalutara', value: 5, children: [] }),
Object({ name: 'Kalutara', value: 4, children: [] }),
Object({ name: 'Kalutara', value: 3, children: [] }),
Object({ name: 'Kalutara', value: 2, children: [] })
]
})
Insert cell
{
const svg = getSVG();
drawTreeMap(svg, TEST_DATA);
return svg.node();
}
Insert cell

Purpose-built for displays of data

Observable is your go-to platform for exploring data and creating expressive data visualizations. Use reactive JavaScript notebooks for prototyping and a collaborative canvas for visual data exploration and dashboard creation.
Learn more