Public
Edited
Apr 28
Insert cell
Insert cell
Insert cell
mean_SD_by_country_by_month (1).csv
Type Table, then Shift-Enter. Ctrl-space for more options.

Insert cell
Visualization:
Insert cell
heatmaps1 = {
// Конфігураційні змінні
const width = 250;
const height = 300;
const margin = { top: 100, right: 30, bottom: 30, left: 100 };
const spacingX = width + 85;
const spacingY = height + 70;
const svgWidth = 1110;
const svgHeight = 1580;

const months = [
"December", "January", "February",
"March", "April", "May",
"June", "July", "August",
"September", "October", "November"
];

const seasonMap = {
Winter: ["December", "January", "February"],
Spring: ["March", "April", "May"],
Summer: ["June", "July", "August"],
Autumn: ["September", "October", "November"]
};

const seasonNames = ["Winter", "Spring", "Summer", "Autumn"];

// Деякі допоміжні функції для кольорів та позицій
const seasonColor = (season) => {
const colors = {
Winter: ["#f5fbff", "#0033cc"],
Spring: ["#f5fff5", "#006600"],
Summer: ["#fffff0", "#ffcc00"],
Autumn: ["#fff5e6", "#cc6600"]
};
return d3.scaleLinear().domain([0, 1]).range(colors[season]);
};

const monthToSeason = (monthName) => {
for (const [season, months] of Object.entries(seasonMap)) {
if (months.includes(monthName)) return season;
}
};

const monthPos = (monthName) => {
const idx = months.indexOf(monthName);
return [ (idx % 3) * spacingX, Math.floor(idx / 3) * spacingY ];
};

const createColorbar = (svg, colorInterpolator, maxVal, x, y, label) => {
const barWidth = 13;
const barHeight = 200;

const defs = svg.append("defs");
const linearGradient = defs.append("linearGradient")
.attr("id", `gradient-${label}`)
.attr("x1", "0%").attr("y1", "100%")
.attr("x2", "0%").attr("y2", "0%");

const stops = d3.range(0, 1.01, 0.01);
linearGradient.selectAll("stop")
.data(stops)
.enter()
.append("stop")
.attr("offset", d => `${d * 100}%`)
.attr("stop-color", d => colorInterpolator(d));

svg.append("rect")
.attr("x", x)
.attr("y", y)
.attr("width", barWidth)
.attr("height", barHeight)
.style("fill", `url(#gradient-${label})`);

svg.append("text")
.attr("x", x + barWidth - 8)
.attr("y", y + barHeight)
.attr("alignment-baseline", "hanging")
.attr("font-size", "12px")
.attr("font-weight", "bold")
.text("0");

svg.append("text")
.attr("x", x + barWidth - 8)
.attr("y", y - 5)
.attr("alignment-baseline", "baseline")
.attr("font-size", "12px")
.attr("font-weight", "bold")
.text(maxVal.toFixed(0));

svg.append("text")
.attr("x", x + 15)
.attr("y", y + barHeight/2)
.attr("alignment-baseline", "middle")
.attr("font-size", "12px")
.attr("font-weight", "bold")
.text(label);
};

// Ініціалізація SVG
const svg = d3.create("svg")
.attr("viewBox", [0, 0, svgWidth, svgHeight])
.style("font", "10px sans-serif");

svg.append("rect")
.attr("width", "100%")
.attr("height", "100%")
.attr("fill", "#f9f9f9");

// Текст
svg.append("text")
.attr("x", "2%")
.attr("y", 30)
.attr("text-anchor", "left")
.attr("font-size", "22px")
.attr("font-weight", "bold")
.text("Monthly Country-Level Mean SD Analysis");

svg.append("text")
.attr("x", "2%")
.attr("y", 55)
.attr("text-anchor", "left")
.attr("font-size", "16px")
.attr("fill", "gray")
.text("Mean SD by month in selected countries over the available period");

// Дані
const parsedData = data.map(d => ({
country: d.country,
year: new Date(d.Month).getFullYear(),
month: new Date(d.Month).getMonth(),
monthName: months[new Date(d.Month).getMonth()],
mean_SD: +d.mean_SD
}));

const countries = Array.from(new Set(parsedData.map(d => d.country))).sort();
const years = Array.from(new Set(parsedData.map(d => d.year))).sort();

// Heatmap
months.forEach((monthName, mIndex) => {
const monthData = parsedData.filter(d => d.month === mIndex);

const x = d3.scaleBand()
.domain(years)
.range([margin.left, margin.left + width])
.padding(0.05);

const y = d3.scaleBand()
.domain(countries)
.range([margin.top, margin.top + height])
.padding(0.05);

const season = monthToSeason(monthName);
const colorScale = d3.scaleSequential()
.interpolator(seasonColor(season))
.domain([0, d3.max(monthData, d => d.mean_SD)]);

const [dx, dy] = monthPos(monthName);
const g = svg.append("g")
.attr("transform", `translate(${dx},${dy})`);

g.append("text")
.attr("x", margin.left + width/2)
.attr("y", margin.top - 15)
.attr("text-anchor", "middle")
.attr("font-weight", "bold")
.attr("font-size", "14px")
.text(monthName);

g.selectAll("rect")
.data(monthData)
.join("rect")
.attr("x", d => x(d.year))
.attr("y", d => y(d.country))
.attr("width", x.bandwidth())
.attr("height", y.bandwidth())
.attr("fill", d => d.mean_SD > 0 ? colorScale(d.mean_SD) : "#eee");

g.append("g")
.attr("transform", `translate(0,${margin.top + height})`)
.call(d3.axisBottom(x).tickValues(x.domain().filter(year => year % 5 === 0)))
.selectAll("text")
.attr("transform", "rotate(45)")
.style("text-anchor", "start")
.style("font-size", "8px");

g.append("g")
.attr("transform", `translate(${margin.left},0)`)
.call(d3.axisLeft(y).tickSize(0))
.selectAll("text")
.style("font-size", "8px");
});

seasonNames.forEach((season, rowIndex) => {
svg.append("text")
.attr("x", 20)
.attr("y", rowIndex * spacingY + margin.top + height/2)
.attr("text-anchor", "middle")
.attr("font-size", "14px")
.attr("font-weight", "bold")
.attr("transform", `rotate(-90, 20, ${rowIndex * spacingY + margin.top + height/2})`)
.text("Country");
});

// Бари для heatmap
let seasonIndex = 0;
for (const [season, monthsSeason] of Object.entries(seasonMap)) {
const colorInterpolator = seasonColor(season);
const maxVal = d3.max(parsedData.filter(d => monthsSeason.includes(d.monthName)), d => d.mean_SD);

createColorbar(svg, colorInterpolator, maxVal, 3 * spacingX + 25, seasonIndex * spacingY + 150, season);

seasonIndex++;
}

// Посилання
svg.append("text")
.attr("x", "87%")
.attr("y", svgHeight - 15)
.attr("text-anchor", "middle")
.attr("font-size", "12px")
.attr("fill", "gray")
.text("Reference: Era5, https://www.ecmwf.int/");

return svg.node();
}

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