Public
Edited
Apr 18, 2024
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
{
// Create the SVG container and set its dimensions and styles
const svg = d3
.create("svg")
.attr("width", (width + margin.left + margin.right) * 6 + margin.right)
.attr("height", (height + margin.top + margin.bottom) * 4 + margin.bottom)
.attr(
"style",
"max-width: 100%; height: auto; font-size: 10px; font-family: 'Inter', sans-serif;"
);

//.attr("style", "max-width: 100%; height: auto; height: intrinsic;")
// .attr(
// "style",
// "max-width: 100%; height: auto; font: 10px noto sans-serif;"
// );
//.attr("viewBox", `0 0 ${width} ${height + margin.top + margin.bottom}`)
//.on("click", startAnimation); // Add click event to start the animation

// Select the button and attach a click event listener
d3.select("#startAnimationButton").on("click", function () {
startAnimation();
});

// Define gradients
const defs = svg.append("defs");

defs
.append("linearGradient")
.attr("id", "gradientBlue")
.attr("gradientTransform", "rotate(0)")
.selectAll("stop")
.data([
{ offset: "25%", color: "white", opacity: "0" },
{ offset: "100%", color: "#3f6bee", opacity: "0.7" }
])
.enter()
.append("stop")
.attr("offset", (d) => d.offset)
.attr("stop-color", (d) => d.color)
.attr("stop-opacity", (d) => d.opacity);

defs
.append("linearGradient")
.attr("id", "gradientRed")
.attr("gradientTransform", "rotate(0)")
.selectAll("stop")
.data([
{ offset: "25%", color: "white", opacity: "0" },
{ offset: "100%", color: "#e61f00", opacity: "0.7" }
])
.enter()
.append("stop")
.attr("offset", (d) => d.offset)
.attr("stop-color", (d) => d.color)
.attr("stop-opacity", (d) => d.opacity);

// Setup the scale for the x-axis
const xScale = d3
.scaleTime()
.domain(d3.extent(data, (d) => d.year)) // Assuming 'year' is already a Date object
.range([0, width]);

// Function to start the animation on SVG click
function startAnimation() {
// Group data by labels to create facets
const nestedData = d3.group(data, (d) => d.labels);

const facets = svg
.selectAll("g")
.data(nestedData)
.enter()
.append("g")
.attr("opacity", 0)
.attr(
"transform",
(d, i) =>
`translate(${
(i % 6) * (width + margin.left + margin.right) + margin.left
},${
Math.floor(i / 6) * (height + margin.top + margin.bottom) +
margin.top
})`
);

let surfacetempLine;

// For each facet, sort appearance and animation
facets.each(function (d, i) {
const [label, values] = d;

// Create local y-scale for each facet
const yScale = d3
.scaleLinear()
.domain(d3.extent(values, (v) => v.values))
.range([height, 0])
.nice();

const line1950 = d3
.select(this)
.append("line") // Append line to each facet group
.attr("x1", xScale(new Date("1950")))
.attr("x2", xScale(new Date("1950")))
.attr("y1", 0)
.attr("y2", height)
.attr("stroke", "gray")
.attr("stroke-width", 1)
.attr("stroke-dasharray", "5,5")
.attr("opacity", 0);

// After setting up the axes
if (label === "Surface Temperature") {
surfacetempLine = d3
.select(this)
.append("line")
.attr("x1", 0)
.attr("x2", width)
.attr("y1", yScale(0))
.attr("y2", yScale(0))
.attr("stroke", "black")
.attr("stroke-width", 1)
// .attr("stroke-dasharray", "5,5")
.attr("opacity", 0);
}

d3.select(this)
.transition()
.duration(animationInterval * 0.2)
.delay(animationInterval * i)
.attr("opacity", 1)
.on("end", function () {
// Append y-axis for this facet and set initial opacity to 0
const yAxis = d3
.select(this)
.append("g")
.attr("opacity", 0)
.call(d3.axisLeft(yScale).tickSize(0).tickPadding(6).ticks(4));

// Append x-axis and set initial opacity to 0, with specific ticks
const xAxis = d3
.select(this)
.append("g")
.attr("opacity", 0)
.attr("transform", `translate(0,${height})`)
.call(d3.axisBottom(xScale).tickValues(specificTicks));

// Conditionally remove the x-axis line
if (yScale.domain()[0] !== 0) {
xAxis.select(".domain").remove();
}

// Fade in both axes
yAxis.transition().duration(500).attr("opacity", 1);
xAxis.transition().duration(500).attr("opacity", 1);
surfacetempLine
.transition()
.duration(animationInterval * 1.2)
.attr("opacity", 1);
line1950
.transition()
.duration(animationInterval * 1.2)
.attr("opacity", 1);

// Add grid lines for the y-axis
d3.select(this)
.selectAll(".grid-line")
.data(yScale.ticks(4))
.enter()
.append("line")
.attr("class", "grid-line")
.attr("x1", 0)
.attr("x2", width)
.attr("y1", (d) => yScale(d))
.attr("y2", (d) => yScale(d))
.attr("stroke", "#ccc")
.attr("stroke-width", 1)
.attr("opacity", 0)
.transition()
.duration(500)
.attr("opacity", 0.5);

// Define the area generator for shading under the line
const areaGenerator = d3
.area()
.x((d) => xScale(d.year))
.y0(height)
.y1((d) => yScale(d.values))
.curve(d3.curveNatural);

// Draw the area beneath the line
const area = d3
.select(this)
.append("path")
.datum(values)
.attr("fill", function (d) {
return d[0].trend_type === "Socio-economic Trends"
? "url(#gradientBlue)"
: "url(#gradientRed)";
})
.attr("opacity", 0) // Set initial opacity to 0
.attr("d", areaGenerator);

// Animate the area drawing
area
.transition()
.duration(animationInterval * 1.2) // Sync animation with line
.attr("opacity", 1); // Fade in the area

const whitePath = d3
.select(this)
.append("path")
.datum(values)
.attr("fill", "none")
.attr("stroke", "white")
.attr("stroke-width", 3)
.attr(
"d",
d3
.line()
.x((d) => xScale(d.year))
.y((d) => yScale(d.values))
.curve(d3.curveNatural)
)
.attr("stroke-dasharray", function () {
return `${this.getTotalLength()} ${this.getTotalLength()}`;
})
.attr("stroke-dashoffset", function () {
return this.getTotalLength();
});

whitePath
.transition()
.duration(animationInterval * 0.8) // Draw line over the same interval
.attr("stroke-dashoffset", 0);

// Draw and animate the line
const path = d3
.select(this)
.append("path")
.datum(values)
.attr("fill", "none")
//.attr("stroke", "steelblue")
.attr(
"stroke",
values[0].trend_type === "Socio-economic Trends"
? "#3f6bee"
: "#e61f00"
)
.attr("stroke-width", 1.5)
.attr(
"d",
d3
.line()
.x((d) => xScale(d.year))
.y((d) => yScale(d.values))
.curve(d3.curveNatural)
)
.attr("stroke-dasharray", function () {
return `${this.getTotalLength()} ${this.getTotalLength()}`;
})
.attr("stroke-dashoffset", function () {
return this.getTotalLength();
});

// Sync the line animation with the area animation
path
.transition()
.duration(animationInterval * 0.8) // Draw line over the same interval
.attr("stroke-dashoffset", 0);

// Add the label to the top-left of the facet
const labelFade = d3
.select(this)
.append("text")
.attr("x", 2.5)
.attr("y", 4)
.attr("font-size", "13.5px")
.attr("font-weight", "bold")
.style(
"text-shadow",
"-1px -1px 0 #fff, 1px -1px 0 #fff, -1px 1px 0 #fff, 1px 1px 0 #fff"
)
.text(label)
.call(wrapText, 10);

labelFade.transition().duration(500).attr("opacity", 1);

// Add the Y axis labels
const yLabel = d3
.select(this)
.append("text")
.attr("x", -20)
.attr("y", -12)
.text(values[0].yAxisLabels);

yLabel.transition().duration(500).attr("opacity", 1);
});
});
}

return svg.node();
}
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
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