chart_1 = {
const seasons = [
{ name: "Winter", months: [12, 1, 2], color: d3.interpolateBlues },
{ name: "Spring", months: [3, 4, 5], color: d3.interpolateGreens },
{ name: "Summer", months: [6, 7, 8], color: d3.interpolatePurples },
{ name: "Autumn", months: [9, 10, 11], color: d3.interpolateOranges }
];
const data = processedData;
const countries = sortedCountries;
const years = Array.from(new Set(data.map(d => d.year))).sort(d3.ascending);
const margin = { top: 50, right: 30, bottom: 60, left: 150 };
const subplotMargin = { top: 30, right: 10, bottom: 10, left: 10 };
const legendWidth = 60;
const cellHeight = 15;
const cellWidth = 15;
const cols = 3;
const rows = 4;
const subWidth = 300;
const subHeight = countries.length * cellHeight + subplotMargin.top + subplotMargin.bottom;
const width = cols * subWidth + margin.left + margin.right + legendWidth;
const height = rows * subHeight + margin.top + margin.bottom;
const xScale = d3.scaleBand()
.domain(years)
.range([0, subWidth - subplotMargin.left - subplotMargin.right])
.padding(0.05);
const yScale = d3.scaleBand()
.domain(countries)
.range([0, countries.length * cellHeight])
.padding(0.05);
// Create the SVG
const svg = d3.create("svg")
.attr("width", width)
.attr("height", height)
.attr("viewBox", [0, 0, width, height])
.attr("style", "max-width: 100%; height: auto;");
// Add title
svg.append("text")
.attr("x", width / 2)
.attr("y", margin.top / 2)
.attr("text-anchor", "middle")
.attr("font-size", 20)
.attr("font-weight", "bold")
.text("Seasonal Snow Coverage by Country (2000-2025)");
// Group data by month
const monthlyData = d3.group(data, d => d.month);
// For each season, create a row of subplots
seasons.forEach((season, seasonIndex) => {
svg.append("text")
.attr("x", 20)
.attr("y", margin.top + seasonIndex * subHeight + subHeight / 2)
.attr("text-anchor", "middle")
.attr("font-size", 16)
.attr("font-weight", "bold")
.attr("transform", `rotate(-90, 20, ${margin.top + seasonIndex * subHeight + subHeight / 2})`)
.text(season.name);
// Process data for this season to find min/max values
const seasonData = data.filter(d => season.months.includes(d.month));
const [minVal, maxVal] = d3.extent(seasonData, d => d.mean_SD);
// Create color scale for this season
const colorScale = d3.scaleSequential()
.domain([0, maxVal])
.interpolator(season.color);
// For each month in the season, create a subplot
season.months.forEach((month, monthIndex) => {
const monthData = monthlyData.get(month) || [];
const monthName = new Date(2000, month - 1, 1).toLocaleString('default', { month: 'long' });
const colIndex = monthIndex;
const subplotX = margin.left + colIndex * subWidth;
const subplotY = margin.top + seasonIndex * subHeight;
const subplot = svg.append("g")
.attr("transform", `translate(${subplotX}, ${subplotY})`);
subplot.append("text")
.attr("x", (subWidth - subplotMargin.left - subplotMargin.right) / 2 + subplotMargin.left)
.attr("y", subplotMargin.top / 2)
.attr("text-anchor", "middle")
.attr("font-size", 14)
.attr("font-weight", "bold")
.text(monthName);
if (colIndex === 0) {
countries.forEach((country, i) => {
subplot.append("text")
.attr("x", subplotMargin.left - 5)
.attr("y", subplotMargin.top + yScale(country) + cellHeight / 2)
.attr("text-anchor", "end")
.attr("dominant-baseline", "middle")
.attr("font-size", 10)
.attr("font-weight", "bold")
.text(country);
});
}
if (seasonIndex === rows - 1) {
const xAxis = subplot.append("g")
.attr("transform", `translate(${subplotMargin.left}, ${subplotMargin.top + countries.length * cellHeight + 5})`);
const tickValues = years.filter((_, i) => i % 5 === 0);
xAxis.selectAll("text")
.data(tickValues)
.join("text")
.attr("x", d => xScale(d) + xScale.bandwidth() / 2)
.attr("y", 15)
.attr("text-anchor", "middle")
.attr("font-size", 9)
.text(d => d);
}
// Create the heatmap cells
subplot.append("g")
.attr("transform", `translate(${subplotMargin.left}, ${subplotMargin.top})`)
.selectAll("rect")
.data(monthData)
.join("rect")
.attr("x", d => xScale(d.year))
.attr("y", d => yScale(d.country))
.attr("width", xScale.bandwidth())
.attr("height", yScale.bandwidth())
.attr("fill", d => d.mean_SD === 0 ? "#f5f5f5" : colorScale(d.mean_SD))
.attr("stroke", "#e0e0e0")
.attr("stroke-width", 0.5)
.append("title") // Add tooltip
.text(d => `${d.country}, ${monthName} ${d.year}: ${d.mean_SD}`);
});
// Add color legend at the end of each row
const legendX = margin.left + cols * subWidth + 10;
const legendY = margin.top + seasonIndex * subHeight + subplotMargin.top;
// Create gradient for legend
const legendHeight = countries.length * cellHeight;
const legendSteps = 50;
const legendScale = d3.scaleLinear()
.domain([0, legendSteps - 1])
.range([0, maxVal]);
const legend = svg.append("g")
.attr("transform", `translate(${legendX}, ${legendY})`);
// Create gradient rectangles
for (let i = 0; i < legendSteps; i++) {
legend.append("rect")
.attr("x", 0)
.attr("y", i * (legendHeight / legendSteps))
.attr("width", 15)
.attr("height", legendHeight / legendSteps)
.attr("fill", colorScale(legendScale(legendSteps - 1 - i)));
}
// Add legend title
legend.append("text")
.attr("x", 25)
.attr("y", legendHeight / 2)
.attr("text-anchor", "middle")
.attr("transform", `rotate(90, 25, ${legendHeight / 2})`)
.attr("font-size", 12)
.text("Snow Coverage");
// Add min/max labels
legend.append("text")
.attr("x", 20)
.attr("y", 0)
.attr("dominant-baseline", "hanging")
.attr("font-size", 10)
.text(`${maxVal}`);
legend.append("text")
.attr("x", 20)
.attr("y", legendHeight)
.attr("dominant-baseline", "ideographic")
.attr("font-size", 10)
.text("0");
});
// Add source note
svg.append("text")
.attr("x", width / 2)
.attr("y", height - 10)
.attr("text-anchor", "middle")
.attr("font-size", 10)
.text("Source: https://www.ecmwf.int/");
return svg.node();
}