Public
Edited
May 19, 2023
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
// Make a tree
makePlot = (letter) =>
addTooltips(
Plot.plot({
marks: [
// Plot.frame({ stroke: "#f3f3f3" }),
Plot.tree(
// Feels like there's gotta be a better way to do this sort, but I couldn't figure it out
makeTreeData(topN.filter((d) => d.firstLetter === letter)).data.sort(
(a, b) => {
// Get the total value of all of the child nodes to compare two nodes
let aValue = 0;
a.eachAfter((dd) => (aValue += dd.data?.value || 0));
let bValue = 0;
b.eachAfter((dd) => (bValue += dd.data?.value || 0));
return d3.ascending(bValue, aValue);
}
),
{
path: (d) => d.id,
r: 0, // no dots
// If someone types in, show that as the first piece of text
text: (d) =>
d.depth === 0 && search.length
? search.toUpperCase()
: nameof(d.id),
// Get counts of all children for the tooltip
title: (d) => {
let text = "";
d.eachAfter((dd) => {
if (dd.data?.name)
text = text.concat(
`\n ${letter}${nameof(dd.data.name)} (${d3.format(",")(
dd.data.value
)})`
);
});
return text;
},
strokeWidth: 1,
stroke: "#d3d3d3",
fontSize: (d) => {
// Again, feels like there's gotta be a better way to get the value
let value = 0;
const sum = d.copy().sum((dd) => dd?.value);
d.eachAfter((dd) => (value += dd.data?.value || 0));
return allFontScale(value);
}
}
)
],
axis: null,
width: w,
height,
margin: 5,
inset: 20
}),
{ stroke: "black", "stroke-width": "1px" } // tooltip styles
)
Insert cell
w = width < 500 || search.length ? width : Math.floor(width / 3)
Insert cell
height = width < 500 || search.length ? width / 2 : Math.floor(width / 3)
Insert cell
// d.data.data has a pretty bad code smell :eek:
extent = d3.extent(makeTreeData(topN).leaves(), (d) => d.data.data.value)
Insert cell
allFontScale = d3.scaleLinear().domain(extent).range([6, 18])
Insert cell
Insert cell
candian_baby_names.csv
Type Table, then Shift-Enter. Ctrl-space for more options.

Insert cell
// Top n by first letter
topN = aq
.from(candian_baby_names)
.params({sex, n, search: search.toUpperCase()})
.rename({"First name at birth": "name", VALUE:"value"})
.filter((d) => d.Indicator === "Frequency")
.filter((d) => sex === "Any" ? true : d["Sex at birth"] === sex)
.filter((d) => aq.op.startswith(d.name, search))
.derive({
firstLetter: (d) => d.name[0]
})
.groupby("name", "firstLetter")
.rollup({value: d => aq.op.sum(d.value)}) // sum across sexes
.ungroup()
.select(["name", "firstLetter", "value"])
.groupby("firstLetter")
.orderby(aq.desc("value"))
.filter((d) => aq.op.rank() < n)
.ungroup()
.objects()
Insert cell
// Letter to display in small multiples
letters = search.length ? [search[0].toUpperCase()] : [ ...new Set(topN.map(d => d.firstLetter).sort())]
Insert cell
// Feels like there's a better way to do this...
makeTreeData = (data) => {
data.sort((a, b) => d3.ascending(a.name, b.name));
let prefixes = new Set();
prefixes.add(data[0].firstLetter);
let prevPrefix = data[0].firstLetter;
let i = 0;
for (let i = 0; i < data.length; i++) {
let datum = data[i];
let com = commonPrefix(prevPrefix, datum.name);
console.log(com);
if (com == datum.firstLetter && i < data.length - 1) {
// peak
com = commonPrefix(data[i + 1].name, datum.name);
}
prevPrefix = data[i].name;
prefixes.add(com);
}
prefixes = [...prefixes].sort((a, b) => b.length - a.length);

const sliceUp = (str) => {
let pieces = [];
for (let prefix of prefixes) {
if (str.startsWith(prefix)) {
pieces.push(str.slice(prefix.length));
str = prefix;
}
}
pieces.push(str);
return pieces.reverse().join("/");
};

// Create statification (d3.stratify will interpret slash separation by default)
const stratified = d3.stratify().path((d) => sliceUp(d.name))(data);
// Create a hierarchy of the startified data
const root = d3.hierarchy(stratified);
// Return the summed values across the hierarchy
return root.copy().sum((d) => d.data?.value);
// .sort((a, b) => d3.ascending(b.value ?? b.data?.value, a.value ?? a.data?.value)); // doesn't work?
}
Insert cell
commonPrefix = (a,b) => {
let prefix = "";
for (let i = 0; i < Math.min(a.length, b.length); i++) {
if (a[i] == b[i]) prefix += a[i];
else break;
}
return prefix;
}
Insert cell
commonPrefix("ABACU", "ABAC")
Insert cell
// Modified from: github.com/observablehq/plot/blob/1f790877e680b01e757fc9ea2aae4564ff621030/src/transforms/tree.js#LL257C1-L263C1
// Walk backwards to find the first slash.
function nameof(path) {
let i = path.length;
while (--i > 0) if (path[i] === "/") break;
return path.slice(i + 1);
}
Insert cell
import {addTooltips} from "@mkfreeman/plot-tooltip"
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