Published
Edited
Jun 21, 2021
2 stars
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
chart = {
const root = treemap(data);
const total = root.value;
const svg = d3
.create("svg")
.attr("width", width)
.attr("height", height)
.attr("viewBox", [0, 0, width, height])
.style("font", "10px sans-serif")
.style("transform", "");

const shadow = DOM.uid("shadow");

const percent = d3.format(".1%");

svg
.append("filter")
.attr("id", shadow.id)
.append("feDropShadow")
.attr("flood-opacity", 0.3)
.attr("dx", 0)
.attr("stdDeviation", 3);

let groups = [...d3.group(root, (d) => d.height)];
groups.sort((a, b) => b[0] - a[0]);

const node = svg
.selectAll("g")
.data(groups)
.join("g")
.attr("filter", shadow)
.selectAll("g")
.data((d) => d[1])
.join("g")
.on("click", function () {
let d = d3.select(this).datum();
if (d) changeSelector(d.data.dimension, d.data.name);
})
.attr("transform", (d) => `translate(${d.x0},${d.y0})`);

node.append("title").text(
(d) =>
`${d
.ancestors()
.reverse()
.map((d) => d.data.name)
.join("/")}\n${format(d.value)} (${percent(d.value / total)})`
);

node
.append("rect")
.attr("id", (d) => (d.nodeUid = DOM.uid("node")).id)
.attr("fill", (d) => {
return d.height == 0 ? leafColor(d.data.name) : color(d.height);
})
.attr("width", (d) => d.x1 - d.x0)
.attr("height", (d) => d.y1 - d.y0);

node
.append("clipPath")
.attr("id", (d) => (d.clipUid = DOM.uid("clip")).id)
.append("use")
.attr("xlink:href", (d) => d.nodeUid.href);

node
.append("text")
.attr("clip-path", (d) => d.clipUid)
.selectAll("tspan")
.data((d) => {
let tspans = d.data.name
.split(/(?=[A-Z][^A-Z])/g)
.concat(`${format(d.value)} (${percent(d.value / total)})`);
return tspans;
})
.join("tspan")
.attr("fill-opacity", (d, i, nodes) =>
i === nodes.length - 1 ? 0.7 : null
)
.text((d) => d);

node
.filter((d) => d.children)
.selectAll("tspan")
.attr("dx", 3)
.attr("y", 13);

node
.filter((d) => !d.children)
.selectAll("tspan")
.attr("x", 3)
.attr(
"y",
(d, i, nodes) => `${(i === nodes.length - 1) * 0.3 + 1.1 + i * 0.9}em`
);

return svg.node();
}
Insert cell
Insert cell
currentRootNodeName = {
let sel = [];
arrangement.forEach(i => {
let val = selectors[i].value;
if (val != "All") sel.push(val);
});
if (sel.length != 0) return rootNodeName + ` (${sel.join("/")})`;
return rootNodeName;
}
Insert cell
function getCounts(data, dimensions) {
dimensions = dimensions || Object.keys(data[0]);
let leafMap = new Map();
for (let d of data) {
let datum = dimensions.map(cat => d[cat]);
let dkey = datum.toString();
let leaf = leafMap.get(dkey);
if (leaf) leaf.value++;
else leafMap.set(dkey, { dkey, datum, value: 1 });
}
return [...leafMap.values()].map(d => ((d.value = Math.pow(d.value, 1)), d));
}
Insert cell
function getCatNames(counts) {
let n = counts[0].datum.length;
let catNames = [];
for (let i = 0; i < n; i++) catNames.push(new Set());
for (let count of counts) {
for (let i = 0; i < n; i++) catNames[i].add(count.datum[i]);
}
return catNames.map(c => [...c]);
}
Insert cell
getDimensions = data => Object.keys(data[0])
Insert cell
dimensions = {
let dim = showdim;
dim.reverse();
return dim;
}
Insert cell
selectors = dimensions.map((d, i) => {
let sel = Select(["All"].concat(catNames[i]), { label: d, value: "All" });
sel.querySelector("label").style.cursor = "move";
return sel;
})
Insert cell
function changeSelector(dim, value) {
let i = dimensions.lastIndexOf(dim);
selectors[i].value = value;
selectors[i].dispatchEvent(new CustomEvent("input"));
viewof arrangement.dispatchEvent(new CustomEvent("input"));
}
Insert cell
dimensionsFilter = {
return arrangement.map(i => {
let val = selectors[i].value;
if (val == "All") return new Set([...catNames[i]]);
else return new Set([val]);
});
}
Insert cell
counts = getCounts(categoricalTable, dimensions)
Insert cell
hierarchy = {
const ndim = arrangement.length;
if (ndim != dimensions.length) throw "Dimensions mismatches Arrangement";

const splitDimension = (idim, leaves) => {
let catMap = new Map();
let dim = arrangement[idim];
for (let leaf of leaves) {
let cat = leaf.datum[dim];
if (!dimensionsFilter[idim].has(cat)) continue;
let node = catMap.get(cat);
if (node) node.leaves.push(leaf);
else {
catMap.set(cat, {
name: cat,
dimension: dimensions[arrangement[idim]],
leaves: [leaf]
});
}
}
if (idim == ndim - 1) {
return [...catMap.values()].map(node => {
node.value = node.leaves.map(l => l.value).reduce((a, b) => a + b);
return node;
});
} else {
return [...catMap.values()].map(node => {
let children = splitDimension(idim + 1, node.leaves);
if (children.length > 0) node.children = children;
else {
node.value = 0;
}
return node;
});
}
};
return {
name: currentRootNodeName,
children: splitDimension(0, counts)
};
}
Insert cell
data = hierarchy
Insert cell
treemap = data =>
d3
.treemap()
.tile(tiling[1])
.size([width, height])
.paddingOuter(4)
.paddingTop(20)
.paddingInner(2)
.round(false)(
d3
.hierarchy(data)
.sum(d => d.value)
.sort((a, b) => b.value - a.value)
)
Insert cell
//width = 954
Insert cell
width = 700
Insert cell
Insert cell
format = d3.format(",d")
Insert cell
color = d3.scaleSequential([1, 8], d3.interpolateGreys)
Insert cell
catNames = getCatNames(counts)
Insert cell
leafCatNames = catNames[dimensions.length - 1]
Insert cell
leafColor = d3.scaleOrdinal(
leafCatNames,
d3.schemePaired
/*leafCatNames.map((d, i) =>
d3.interpolateViridis((i + 1) / leafCatNames.length)
)*/
)
Insert cell
Insert cell
import { Checkbox, Select } from "@observablehq/inputs"
Insert cell
import { data as categoricalTable } from "@john-guerra/parallel-sets"
Insert cell
import { SortableList } from "@esperanc/sortable-list"
Insert cell
d3 = require("d3@6")
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