Public
Edited
Mar 5
2 forks
Insert cell
Insert cell
Insert cell
countries.geo.json
Type SQL, then Shift-Enter. Ctrl-space for more options.

Insert cell
GlobalLandTemperaturesByCountry.csv
Type Table, then Shift-Enter. Ctrl-space for more options.

Insert cell
countriesData = await fetch("https://cdn.jsdelivr.net/npm/world-atlas@2/countries-110m.json")
.then(response => response.json())
Insert cell
rawTempData = await FileAttachment("GlobalLandTemperaturesByCountry.csv").csv()
Insert cell
tempData = rawTempData.map(d => ({
dt: d.dt,
AverageTemperature: d.AverageTemperature ? +d.AverageTemperature : null,
AverageTemperatureUncertainty: d.AverageTemperatureUncertainty ? + d.AverageTemperatureUncertainty : null,
Country: d.Country
}))
Insert cell
years = [...new Set(tempData.map(d => d.dt.split("-")[0]))].sort()
Insert cell
months = [
{value: "01", label: "January"},
{value: "02", label: "February"},
{value: "03", label: "March"},
{value: "04", label: "April"},
{value: "05", label: "May"},
{value: "06", label: "June"},
{value: "07", label: "July"},
{value: "08", label: "August"},
{value: "09", label: "September"},
{value: "10", label: "October"},
{value: "11", label: "November"},
{value: "12", label: "December"}
]
Insert cell
import { Scrubber } from "@mbostock/scrubber";
Insert cell
viewof selectedYear = Scrubber(years, {
autoplay: false,
delay: 40,
loop: false
});
Insert cell
viewof selectedMonth = Inputs.select(months, {
label: "Month",
format: d => d.label,
value: months.find(m => m.value === "01")
})
Insert cell
tempExtent = d3.extent(tempData.filter(d => d.AverageTemperature !== null),
d => +d.AverageTemperature)
Insert cell
minTemp = Math.floor(tempExtent[0])
Insert cell
maxTemp = Math.ceil(tempExtent[1])
Insert cell
colorScale = d3.scaleLinear()
.domain([minTemp, 0, maxTemp])
.range(["#1d91c0", "#f7f7f7", "#cb181d"])
.clamp(true)
Insert cell
getCountryMappedName = function(name) {
const mapping = {
"United States of America": "United States",
"Dominican Rep.": "Dominican Republic",
"Falkland Is.": "Falkland Islands (Islas Malvinas)",
"Czechia": "Czech Republic",
"Bosnia and Herz.": "Bosnia And Herzegovina",
"Kosovo": "Macedonia",
"Côte d'Ivoire": "Côte D'Ivoire",
"Guinea-Bissau": "Guinea Bissau",
"eSwatini": "Swaziland",
"W. Sahara": "Western Sahara",
"Dem. Rep. Congo": "Congo (Democratic Republic Of The)",
"S. Sudan": "Sudan",
"Central African Rep.": "Central African Republic",
"Eq. Guinea": "Equatorial Guinea",
"Somaliland": "Somalia",
"Myanmar": "Burma",
"Brunei": "Malaysia",
"TimorLeste": "Timor Leste",

"Solomon Is.": "Solomon Islands",
"Vanuatu": "New Caledonia",
};
return mapping[name] || name;
}
Insert cell
getTemperature = (country, year, month) => {
const date = `${year}-${month}-01`;
const record = tempData.find(d => d.dt === date && d.Country === country);
return record && record.AverageTemperature !== null ? record.AverageTemperature : null;
}
Insert cell
map = {
const width = 960;
const height = 500;
const svg = d3.create("svg")
.attr("viewBox", [0, 0, width, height])
.attr("width", width)
.attr("height", height)
.style("max-width", "100%")
.style("height", "auto");
const tooltip = d3.select(document.body)
.append("div")
.style("position", "absolute")
.style("visibility", "hidden")
.style("background-color", "white")
.style("border", "solid")
.style("border-width", "1px")
.style("border-radius", "5px")
.style("padding", "10px");
const projection = d3.geoNaturalEarth1()
.fitSize([width, height], topojson.feature(countriesData, countriesData.objects.countries));
const path = d3.geoPath()
.projection(projection);
svg.append("g")
.selectAll("path")
.data(topojson.feature(countriesData, countriesData.objects.countries).features)
.join("path")
.attr("d", path)
.attr("fill", d => {
const countryName = d.properties.name;
const mappedName = getCountryMappedName(countryName);
const temp = getTemperature(mappedName, selectedYear, selectedMonth.value);
return temp !== null ? colorScale(temp) : "#ccc";
})
.attr("stroke", "#fff")
.attr("stroke-width", 0.5)
.on("mouseover", function(event, d) {
d3.select(this).attr("stroke-width", 1.5);
const countryName = d.properties.name;
const mappedName = getCountryMappedName(countryName);
const temp = getTemperature(mappedName, selectedYear, selectedMonth.value);
tooltip
.style("visibility", "visible")
.html(`<strong>${countryName}</strong><br>Temperature: ${temp !== null ? temp.toFixed(2) + "°C" : "No data"}`);
tooltip
.style("left", (event.pageX + 10) + "px")
.style("top", (event.pageY - 28) + "px");
})
.on("mousemove", function(event) {
tooltip
.style("left", (event.pageX + 10) + "px")
.style("top", (event.pageY - 28) + "px");
})
.on("mouseout", function() {
d3.select(this).attr("stroke-width", 0.5);
tooltip.style("visibility", "hidden");
});
const legendWidth = 300;
const legendHeight = 20;
const legend = svg.append("g")
.attr("transform", `translate(${width - legendWidth - 10}, ${height - 40})`);
const defs = svg.append("defs");
const linearGradient = defs.append("linearGradient")
.attr("id", "temperature-gradient")
.attr("x1", "0%")
.attr("y1", "0%")
.attr("x2", "100%")
.attr("y2", "0%");
linearGradient.selectAll("stop")
.data([
{offset: "0%", color: colorScale(minTemp)},
{offset: "50%", color: colorScale(0)},
{offset: "100%", color: colorScale(maxTemp)}
])
.join("stop")
.attr("offset", d => d.offset)
.attr("stop-color", d => d.color);
legend.append("rect")
.attr("width", legendWidth)
.attr("height", legendHeight)
.style("fill", "url(#temperature-gradient)");
legend.selectAll(".legend-tick")
.data([minTemp, 0, maxTemp])
.join("g")
.attr("class", "legend-tick")
.attr("transform", d => `translate(${legendWidth * (d - minTemp) / (maxTemp - minTemp)}, 0)`)
.call(g => g.append("line")
.attr("y1", legendHeight)
.attr("y2", legendHeight + 6)
.attr("stroke", "black"))
.call(g => g.append("text")
.attr("y", legendHeight + 16)
.attr("text-anchor", "middle")
.attr("font-size", "10px")
.text(d => d + "°C"));
legend.append("text")
.attr("x", legendWidth / 2)
.attr("y", -5)
.attr("text-anchor", "middle")
.attr("font-size", "12px")
.text("Average Temperature");
return svg.node();
}
Insert cell
update = {
d3.select(map)
.selectAll("path")
.attr("fill", d => {
const countryName = d.properties.name;
const mappedName = getCountryMappedName(countryName);
const temp = getTemperature(mappedName, selectedYear, selectedMonth.value);
return temp !== null ? colorScale(temp) : "#ccc";
});
return "Map updated";
}
Insert cell
viewof selectedCountry = Inputs.select(
[...new Set(tempData.map(d => d.Country))].sort(),
{ label: "Select Country" }
);

Insert cell
countryTemperatureData = months.map(m => ({
month: m.label,
temperature: getTemperature(selectedCountry, selectedYear, m.value)
}));

Insert cell
areaChart = {
const width = 800;
const height = 400;
const margin = { top: 20, right: 30, bottom: 40, left: 50 };
const svg = d3.create("svg")
.attr("width", width)
.attr("height", height)
.style("max-width", "100%")
.style("height", "auto");
const x = d3.scalePoint()
.domain(months.map(m => m.label))
.range([margin.left, width - margin.right])
.padding(0.5);
const y = d3.scaleLinear()
.domain([
d3.min(countryTemperatureData, d => d.temperature) - 2,
d3.max(countryTemperatureData, d => d.temperature) + 2
])
.range([height - margin.bottom, margin.top]);
const area = d3.area()
.x(d => x(d.month))
.y0(y(0))
.y1(d => y(d.temperature || 0))
.curve(d3.curveMonotoneX);
const line = d3.line()
.x(d => x(d.month))
.y(d => y(d.temperature || 0))
.curve(d3.curveMonotoneX);

svg.append("g")
.attr("transform", `translate(0, ${height - margin.bottom})`)
.call(d3.axisBottom(x));
svg.append("g")
.attr("transform", `translate(${margin.left},0)`)
.call(d3.axisLeft(y));


svg.append("text")
.attr("x", width / 2)
.attr("y", margin.top)
.attr("text-anchor", "middle")
.attr("font-size", "16px")
.text(`Temperature Trend for ${selectedCountry} (${selectedYear})`);
svg.append("path")
.datum(countryTemperatureData)
.attr("fill", "steelblue")
.attr("opacity", 0.6)
.attr("d", area);
svg.append("path")
.datum(countryTemperatureData)
.attr("fill", "none")
.attr("stroke", "black")
.attr("stroke-width", 1.5)
.attr("d", line);

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