Public
Edited
Dec 21, 2023
Insert cell
viewof table = Inputs.table(pe)
Insert cell
Insert cell
Plot.plot({
height: 1000,
x: {axis: "top", type: "ordinal", tickFormat: "", inset: 90, label: null},
y: {axis: null, inset: 20},
marks: [
Plot.line(pe, {x: "data_year", y: "pe_ct_per_1000", z: "pub_agency_name"}),
d3.groups(pe, (d) => d.year === 1970)
.map(([left, receipts]) =>
Plot.text(receipts, occlusionY({
x: "year",
y: "receipts",
text: left
? (d) => `${d.pub_agency_name} ${d.pe_ct_per_1000}`
: (d) => `${d.pe_ct_per_1000} ${d.pub_agency_name}`,
textAnchor: left ? "end" : "start",
dx: left ? -3 : 3,
radius: 5.5
}))
)
],
caption: "Current receipts of government as a percentage of gross domestic product"
})
Insert cell
// OcclusionY adds an initializer that shifts nodes vertically with a tiny force simulation.
occlusionY = ({radius = 6.5, ...options} = {}) => Plot.initializer(options, (data, facets, { y: {value: Y}, text: {value: T} }, {y: sy}, dimensions, context) => {
for (const index of facets) {
const unique = new Set();
const nodes = Array.from(index, (i) => ({
fx: 0,
y: sy(Y[i]),
visible: unique.has(T[i]) // remove duplicate labels
? false
: !!unique.add(T[i]),
i
}));
d3.forceSimulation(nodes.filter((d) => d.visible))
.force("y", d3.forceY(({y}) => y)) // gravitate towards the original y
.force("collide", d3.forceCollide().radius(radius)) // collide
.stop()
.tick(20);
for (const { y, node, i, visible } of nodes) Y[i] = !visible ? NaN : y;
}
return {data, facets, channels: {y: {value: Y}}};
})
Insert cell
// receipts = FileAttachment("pe_1960_2022_de@1.csv").csv({ typed: true })
Insert cell
pe = FileAttachment("pe_1960_2022_de@2.csv").csv({ typed: true })
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