{
const margin = { top: 30, right: 40, bottom: 30, left: 25 };
const totalWidth = 960;
const height = 350 - margin.top - margin.bottom;
const dataByDecade = d3.group(data, (d) => d.decade);
const dur = 1400;
const data2023 = data.filter((d) => d.year === 2023);
const lastData2023 = data2023[data2023.length - 1];
const data2024 = data.filter((d) => d.year === 2024);
const lastData2024 = data2024[data2024.length - 1];
const svg = d3
.create("svg")
.attr("viewBox", `0 0 ${totalWidth} ${height + margin.top + margin.bottom}`)
.attr("style", "max-width: 100%; height: auto; font: 10px sans-serif;");
let facetWidth =
(totalWidth - margin.left - margin.right) / dataByDecade.size;
const y = d3
.scaleLinear()
.domain([0, d3.max(data, (d) => d.ano_pi)])
.range([height, 0]);
const minAnoPi = d3.min(data, (d) => d.ano_pi);
const maxAnoPi = d3.max(data, (d) => d.ano_pi);
const x = d3
.scaleTime()
.domain(d3.extent(data, (d) => d.dummy_date))
.range([0, facetWidth - margin.right]);
const xSuperimposed = d3
.scaleTime()
.domain(d3.extent(data, (d) => d.dummy_date))
.range([0, totalWidth - margin.left - margin.right]);
const lineGenerator = d3
.line()
.x((d) => x(d.dummy_date))
.y((d) => y(d.ano_pi));
let isMerged = false;
function updateFacets() {
facetWidth = isMerged
? totalWidth - margin.left - margin.right
: (totalWidth - margin.left - margin.right) / dataByDecade.size;
const currentXScale = isMerged ? xSuperimposed : x;
lineGenerator.x((d) => currentXScale(d.dummy_date));
svg
.selectAll(".facet")
.transition()
.duration(dur)
.attr("transform", (_, i) =>
isMerged
? `translate(${margin.left}, ${margin.top})`
: `translate(${margin.left + i * facetWidth}, ${margin.top})`
);
svg
.selectAll(".decade-label")
.transition()
.duration(dur)
.style("opacity", isMerged ? 0 : 1);
svg
.selectAll(".twentythree-label")
.transition()
.duration(dur)
.attr("x", currentXScale(lastData2023.dummy_date))
.attr("y", y(lastData2023.ano_pi));
svg
.selectAll(".twentyfour-label")
.transition()
.duration(dur)
.attr("x", currentXScale(lastData2024.dummy_date))
.attr("y", y(lastData2024.ano_pi));
svg
.selectAll(".facet .tick line")
.transition()
.duration(dur)
.style("opacity", isMerged ? 0 : 1);
svg
.selectAll(".x-axis")
.transition()
.duration(dur)
.call(
d3
.axisBottom(currentXScale)
.tickFormat(d3.timeFormat("%B"))
.tickValues([
new Date("2000-01-01"),
new Date("2000-06-01"),
new Date("2000-12-01")
])
)
.call((g) => g.select(".domain").attr("stroke", "none"))
.call((g) => g.selectAll(".tick line").attr("stroke", "#777"));
svg
.selectAll(".line")
.transition()
.duration(dur)
.attr("d", (d) => lineGenerator(d));
console.log("Facet Width:", facetWidth);
console.log("Is Merged:", isMerged);
}
setInterval(() => {
isMerged = !isMerged;
updateFacets();
}, 3000);
dataByDecade.forEach((values, decade, i) => {
const xOffset = margin.left + i * facetWidth;
const facetGroup = svg
.append("g")
.attr("class", "facet")
.attr("transform", `translate(${xOffset}, ${margin.top})`);
facetGroup
.append("text")
.attr("class", "decade-label")
.attr("x", facetWidth / 2)
.attr("y", 0 - margin.top / 2)
.text(decade + "s")
.style("font-size", "16px")
.attr("text-anchor", "middle");
facetGroup
.append("g")
.attr("class", "x-axis")
.attr("transform", `translate(0, ${height})`)
.call(
d3
.axisBottom(x)
.tickFormat(d3.timeFormat("%b"))
.tickValues([
new Date("2000-01-01"),
new Date("2000-06-01"),
new Date("2000-12-01")
])
)
.call((g) => g.select(".domain").remove())
.call((g) => g.selectAll(".tick line").attr("stroke", "#777"));
facetGroup
.append("g")
.call(d3.axisLeft(y).tickValues([0, 0.5, 1, 1.5, 2]))
.call((g) => g.select(".domain").remove())
.call((g) => g.selectAll(".tick line").attr("stroke", "#777"))
.call((g) =>
g
.selectAll("line")
.attr("x2", facetWidth - margin.right)
.attr("stroke", "#ddd")
);
const line = d3
.line()
.x((d) => x(d.dummy_date))
.y((d) => y(d.ano_pi));
const gradient = svg
.append("linearGradient")
.attr("id", "line-gradient")
.attr("gradientUnits", "userSpaceOnUse")
.attr("x1", 0)
.attr("y1", y(minAnoPi))
.attr("x2", 0)
.attr("y2", y(maxAnoPi))
.selectAll("stop")
.data([
{ offset: "5%", color: "#CCCCCC" },
{ offset: "15%", color: "#FFC300" },
{ offset: "40%", color: "#FF5733" },
{ offset: "70%", color: "#C70039" },
{ offset: "90%", color: "#900C3F" },
{ offset: "98%", color: "#581845" }
])
.enter()
.append("stop")
.attr("offset", function (d) {
return d.offset;
})
.attr("stop-color", function (d) {
return d.color;
});
const dataByYearWithinDecade = d3.group(values, (d) => d.year);
dataByYearWithinDecade.forEach((yearValues, year) => {
facetGroup
.selectAll(`.line-${year}`)
.data([yearValues])
.join("path")
.attr("class", `line line-${year}`)
.attr("fill", "none")
.attr("stroke", "url(#line-gradient)")
.attr("d", lineGenerator);
});
if (decade === 2020) {
const highlightYears = [2024];
highlightYears.forEach((year) => {
const yearData = data.filter((d) => d.year === year);
if (yearData.length > 0) {
facetGroup
.selectAll(`.line-outline-${year}`)
.data([yearData])
.join("path")
.attr("class", `line line-outline-${year}`)
.attr("fill", "none")
.attr("stroke", "white")
.attr("stroke-width", 6)
.attr("d", lineGenerator);
facetGroup
.selectAll(`.line-highlight-${year}`)
.data([yearData])
.join("path")
.attr("class", `line line-highlight-${year}`)
.attr("fill", "none")
.attr("stroke", "url(#line-gradient)")
.attr("stroke-width", 3.5)
.attr("d", lineGenerator);
}
});
}
if (decade === 2020) {
facetGroup
.append("text")
.attr("class", "twentythree-label")
.attr("x", x(lastData2023.dummy_date))
.attr("y", y(lastData2023.ano_pi))
.attr("dy", "0px")
.attr("dx", "5px")
.attr("text-anchor", "start")
.style("font-size", "16px")
.style("fill", "black")
.style("font-weight", "bold")
.text("2023");
facetGroup
.append("text")
.attr("class", "twentyfour-label")
.attr("x", x(lastData2024.dummy_date))
.attr("y", y(lastData2024.ano_pi))
.attr("dy", "0px")
.attr("dx", "5px")
.attr("text-anchor", "start")
.style("font-size", "16px")
.style("fill", "black")
.style("font-weight", "bold")
.style(
"text-shadow",
"-1px -1px 0 #fff, 1px -1px 0 #fff, -1px 1px 0 #fff, 1px 1px 0 #fff"
)
.text("2024");
}
});
updateFacets();
return svg.node();
}