Public
Edited
Nov 9, 2023
Fork of Beeswarm
Insert cell
Insert cell
Insert cell
viewof year = Inputs.range(yearExtent, {
value: 1970,
step: 1,
label: "Year"
})
Insert cell
chart = {
const svg = d3
.create("svg")
.attr("width", width + marginLeft + marginRight)
.attr("height", height + marginTop + marginBottom);

const g = svg
.append("g")
.attr("transform", `translate(${[marginLeft, marginTop]})`);

svg
.append("g")
.attr("transform", `translate(${marginLeft},${height - marginBottom * 2})`)
.call(d3.axisBottom(x).tickSizeOuter(0));

const yearG = svg.append("g");
const indicatorG = svg.append("g");
const loadingG = svg.append("g");

indicatorG
.selectAll("text")
.data([indicatorId], () => "indicatorId")
.join("text")
.attr("class", "title")
.text((d) => indicatorIdToNames.get(d))
.attr("font-size", "2rem")
.attr("x", 50)
.attr("y", 50);

const { dataByYears, bestProgress, worstProgress, isHighlighted } = chartData;

const update = (y) => {
const yearData = dataByYears.get(y);
g.selectAll("circle")
.data(yearData, (x) => x.data.geoUnit)
.join("circle")
.transition()
.duration(y === yearExtent[0] ? 0 : 250)
.attr("fill", (b) =>
bestProgress.has(b.data.geoUnit)
? "#016b3d"
: worstProgress.has(b.data.geoUnit)
? "#d4212c"
: "#ddd"
)
.attr("fill-opacity", (b) =>
bestProgress.has(b.data.geoUnit) ? 0.8 : 0.8
)
.attr("title", (b) => b.data.geoUnit)
.attr("cx", (d) => d.x)
.attr("cy", (d) => d.y)
.attr("r", (d) =>
bestProgress.has(d.data.geoUnit) || worstProgress.has(d.data.geoUnit)
? 5
: 3
);

const progressData = yearData.filter((x) => isHighlighted(x.data));
g.selectAll("text")
.data(progressData, (x) => x.data.geoUnit)
.join("text")
.transition()
.duration(y === yearExtent[0] ? 0 : 250)
.attr("fill", (b) =>
isHighlighted(b.data) ? color(b.data.geoUnit) : "#eee"
)
.attr("font-family", "sans-serif")
.attr("font-size", "small")
.attr("title", (b) => b.data.geoUnit)
.attr("x", (d) => d.x - 20)
.attr("y", (d) => d.y - 20)
.text((b) => b.data.geoUnit);

yearG
.selectAll("text")
.data([y], () => "year")
.join("text")
.attr("class", "title")
.text(y)
.attr("font-size", "3rem")
.attr("fill", "#bbb")
.attr("x", 50)
.attr("y", 100);
};

const setLoading = (text) => {
loadingG
.selectAll("text")
.data([text], (x) => "text")
.join("text")
.text((d) => d)
.attr("x", width - 100)
.attr("y", 24);
};

update(yearExtent[0]);

return Object.assign(svg.node(), { update, setLoading });
}
Insert cell
chartData = {
const deltas = [
...d3
.rollup(
data,
(countryData) => {
const sorted = countryData.sort((a, b) =>
d3.ascending(a.year, b.year)
);
return sorted[sorted.length - 1].value - sorted[0].value;
},
(x) => x.geoUnit
)
.entries()
]
.sort((a, b) => d3.descending(a[1], b[1]))
.filter((x) => x[0]);
const bestProgress = new Set(deltas.slice(0, 5).map((x) => x[0]));
const worstProgress = new Set(deltas.slice(-5).map((x) => x[0]));
const isHighlighted = (d) =>
bestProgress.has(d.geoUnit) || worstProgress.has(d.geoUnit);

const years = d3.range(yearExtent[0], yearExtent[1] + 1);
const allData = [];

console.log(years.length);
console.time("compute");
for (let y of years) {
await Promises.tick(1);
allData.push([
y,
beeswarm(
data
.filter((x) => x.year === y)
.sort((a, b) =>
d3.ascending(isHighlighted(a) ? 1 : 0, isHighlighted(b) ? 1 : 0)
)
)
]);
}

console.timeEnd("compute");
return {
bestProgress,
worstProgress,
isHighlighted,
dataByYears: new Map(allData)
};
}
Insert cell
yearExtent = d3.extent(data, (d) => d.year);
Insert cell
x = d3
.scaleLinear()
.domain(d3.extent(data, (d) => d?.["value"]))
.range([marginLeft, width - marginRight])
Insert cell
Insert cell
Insert cell
Insert cell
width = 928;
Insert cell
height = 300
Insert cell
marginTop = 20
Insert cell
marginRight = 20
Insert cell
marginBottom = 20
Insert cell
marginLeft = 20
Insert cell
radius = 3
Insert cell
padding = 1.5
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
mainindicators = FileAttachment("mainIndicators.json").json()
Insert cell
beeswarm = beeswarmForce()
.x((d) => x(d.value))
.y(height / 2)
.r((d) => 3)
Insert cell
Insert cell
indicatorIdToNames = new Map(mainindicators.map((x) => [x.id, x.name]))
Insert cell
Insert cell
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