legend = () => {
const wrapper = d3.create("div");
wrapper.append("div")
.style("font-family", franklinLight)
.style("line-height", "22px")
.style("margin-bottom", "4px")
.style("text-align", "center")
.html("Change in expected arrival<br />of leaves, 1981-2023");
const margin = { top: 1, bottom: 32, right: 0 };
const width = 250;
const height = 16;
const scale = d3.scaleBand()
.domain(legendValues)
.range([0, width]);
const svg = wrapper.append("svg")
.attr("width", width + margin.right)
.attr("height", height + margin.top + margin.bottom)
.attr("font-family", franklinLight)
.style("margin", "0 auto")
.style("display", "table")
.style("overflow", "visible");
const g = svg.append("g")
.attr("transform", `translate(0, ${margin.top})`)
const tick = g.selectAll(".tick")
.data(legendValues)
.join("g")
.attr("class", "tick")
.attr("transform", d => `translate(${scale(d)})`);
const epsilon = 1e-6;
tick.append("rect")
.attr("fill", d => {
const v = d > 0 ? d - epsilon : d + epsilon;
return colorScale(v)
})
.attr("height", height)
.attr("x", -0.5)
.attr("width", (_, i, e) => scale.bandwidth() + (i < e.length - 1 ? 1 : 0));
const tickValues = [...d3.range(-28, 0, 7), -1, ...d3.range(0, 21, 7)]
tick.append("line")
.filter(d => tickValues.includes(d))
.attr("stroke", "black")
.attr("transform", d => `translate(${-0.5 + (d >= valuesEarlier[0] ? scale.bandwidth() : 0)})`)
.attr("y1", height)
.attr("y2", height + 4);
tick.append("text")
.filter(d => tickValues.includes(d))
.attr("font-size", 14)
.attr("text-anchor", "middle")
.attr("transform", d => `translate(${-0.5 + (d >= valuesEarlier[0] ? scale.bandwidth() : 0)})`)
.attr("y", height + 17)
.html((d, i, e) => d === valuesEarlier[0] ? "0" : `${Math.abs(d)}`);
const adverb = g.selectAll(".adverb")
.data(["days earlier", "days later"])
.join("text")
.attr("class", "adverb")
.attr("font-size", 14)
.attr("text-anchor", "middle")
.attr("x", (_, i) => i * width)
.attr("y", height + 30)
.text(d => d)
const noTrend = svg.append("g")
.attr("transform", `translate(${[width + 24, margin.top]})`);
noTrend.append("rect")
.attr("height", height)
.attr("width", scale.bandwidth())
.attr("fill", "none")
.attr("shape-rendering", "crispEdges")
.attr("stroke", "#000");
noTrend.append("text")
.attr("font-size", 14)
.attr("x", scale.bandwidth() + 4)
.attr("y", height / 2 + 4)
.text("No trend")
return wrapper.node();
}