async function createPresidentChart_yearly(data, visibility) {
const svg = d3
.create("svg")
.attr("width", width)
.attr("height", height)
.attr("id", "decretometro-presidents");
const n = Math.floor(data.perYear);
const numberColumns = 5;
const nPerColumn = Math.ceil(n / numberColumns);
const resto = n % numberColumns;
const columnsToAdjust = resto ? numberColumns - resto : 0;
const bckgGroup = svg.append("g").attr("id", "bckgGroup");
bckgGroup
.append("rect")
.attr("width", width)
.attr("height", height)
.style("fill", primaryColor);
const papersGroup = svg.append("g").attr("id", "papersGroup");
const horizOffset = 47;
const landscapeGroup = papersGroup
.selectAll("g")
.data(d3.range(numberColumns))
.join("g")
.attr("data-column", (d) => d)
.style(
"transform",
(d, i) => `translate(${horizOffset + d * 40}px, ${Math.random() * 10}px)`
);
// Number of documents per column to paint
const itemsColum = nPerColumn;
const columnGroup = landscapeGroup
.selectAll("g")
.data(d3.range(itemsColum))
.join("g")
.style("transform", (d, i) => `translate(${0}px, ${-100}px)`);
// Easiest way: hide one element on resto ones
if (resto) {
const lastColumns = landscapeGroup
.filter((d, i) => i >= resto)
.attr("data-adjust", true)
.select("g")
.style("opacity", 0);
}
columnGroup
.append("rect")
.attr("x", (d, i) => Math.random() * 3)
.attr("y", (d, i) => Math.random() * 3)
.attr("width", docWidth * 0.7)
.attr("height", docHeight * 0.7)
.style("stroke", "grey")
.style("fill", "white")
.attr("transform", `scale(1, .7) rotate(${docAngle})`);
// LEGEND
const textGroup = svg
.append("g")
.attr("id", "textGroup")
.style("fill", "white")
.style("text-anchor", "middle");
// First line: counter
const textCounter = textGroup
.append("text")
.attr("x", width / 2)
.attr("y", height - 70);
const dynamicText = textCounter
.append("tspan")
.text(n)
.style("font-size", "2.2rem");
textCounter.append("tspan").attr("dx", 5).text(`páginas/año`);
// Second line: president name
const presidentLabel = textGroup
.append("text")
.attr("x", width / 2)
.attr("y", height - 40)
.text(data.label)
.style("font-size", "1.2rem");
// IMAGE
const scaleFactor = 0.55;
const widthImg = 306 * scaleFactor;
const heightImg = 166 * scaleFactor;
const imgGroup = svg.append("g").attr("id", "imgGroup");
const img = imgGroup
.append("image")
.attr("width", widthImg)
.attr("height", heightImg)
.attr("x", width / 2 - widthImg / 2 + 10)
.attr("y", -100)
.style("opacity", 0.9)
.attr("class", data.name);
// Pick a different image on each case
imgGroup
.select(".Aznar")
.attr("href", await FileAttachment("aznar-02.png").url());
imgGroup
.select(".Zapatero")
.attr("href", await FileAttachment("zapatero-02.png").url());
imgGroup
.select(".Rajoy")
.attr("href", await FileAttachment("rajoy-02.png").url());
imgGroup
.select(".Sanchez")
.attr("href", await FileAttachment("sanchez-02.png").url());
// ANIMATION - waiting visibility
// "For reusable code, pass the visibility function across cells"
visibility().then(function () {
// The chart is launched after becoming visible, but we need a small delay.
const initialDelay = 800;
// 1. Papers animation
const columnFactorDelay = 300;
columnGroup
.transition()
.duration(300)
//.delay((d, i) => i * 25)
.delay(function (d, i) {
const columnId = d3.select(this.parentNode).attr("data-column");
return columnFactorDelay * columnId + i * 25;
})
.style("transform", (d, i) => `translate(${0}px, ${scaleYYear(i)}px)`);
// 2. Text animation
// https://observablehq.com/@d3/transition-texttween
// Is one column total timespan plus the offset of the other columns
const totalDuration = nPerColumn * 25 + columnFactorDelay * numberColumns;
dynamicText
.transition()
.duration(totalDuration + 200)
.textTween(() => (t) => `${d3.format(",.0f")(d3.interpolate(0, n)(t))}`);
// 3. Image animation
img
.transition()
.duration(500)
.delay(totalDuration)
.attr("y", scaleYYear(nPerColumn) - heightImg + 5);
});
return svg.node();
}