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

One platform to build and deploy the best data apps

Experiment and prototype by building visualizations in live JavaScript notebooks. Collaborate with your team and decide which concepts to build out.
Use Observable Framework to build data apps locally. Use data loaders to build in any language or library, including Python, SQL, and R.
Seamlessly deploy to Observable. Test before you ship, use automatic deploy-on-commit, and ensure your projects are always up-to-date.
Learn more