chart = {
const width = 600;
const height = 500;
const margin = { top: 100, right: 30, bottom: 40, left: 40 };
const innerWidth = width - margin.left - margin.right;
const innerHeight = height - margin.top - margin.bottom;
let radius = 1.5;
const parseDate = d3.timeParse("%Y-%m-%d");
const svg = d3.create("svg")
.attr("width", width)
.attr("height", height)
.style("background-color", "#fff0e5");
const g = svg.append("g")
.attr("transform", `translate(${margin.left}, ${margin.top})`);
const monthNames = ["J", "F", "M", "A", "M", "J", "J", "A", "S", "O", "N", "D"];
const dataByMonth = {};
monthNames.forEach((month, index) => {
dataByMonth[index] = monthNames[index - 1];
})
const existingDates = new Set(data_origin.map(d => new Date(d.date).getTime()));
const data = data_origin.concat(update.filter(u => !existingDates.has(u.date.getTime())));
data.forEach(d => {
d.X2t = +d.X2t;
d.month = +d.month;
d.monthString = dataByMonth[d.month];
});
const minTemp = Math.floor(d3.min(data, d => d.X2t));
const maxTemp = Math.ceil(d3.max(data, d => d.X2t));
const minDate = d3.min(data, d => d.date);
const maxDate = d3.max(data, d => d.date);
const sorted = data.slice().sort((a, b) => new Date(a.date) - new Date(b.date));
const middleDate = sorted[Math.floor(sorted.length / 2)].date;
const yScale = d3.scaleLinear()
.domain([minTemp, maxTemp])
.range([innerHeight, 0])
.nice();
const xScale = d3.scaleLinear()
.domain([0, 12])
.range([0, innerWidth]);
let colorScale = d3.scaleLinear()
.domain([minDate, middleDate, maxDate])
.range(["#fdd63b", "#ba0203", "#4a0005"])
const xAxis = d3.axisBottom(xScale)
.tickFormat( (d, i) => monthNames[i - 1])
.ticks(12);
const yAxis = d3.axisLeft(yScale)
.tickFormat(d => `${d}C`)
.ticks(6)
.tickSize(-width)
g.append("g")
.attr("class", "y-axis")
.call(yAxis)
.selectAll('.tick line')
.style("stroke", "#eae8e8");
g.append("g")
.attr("class", "x-axis")
.attr("transform", `translate(0, ${innerHeight})`)
.call(xAxis)
.selectAll('.tick line')
.style("display", "none");
const dots = g.selectAll(`.dot`)
.data(data)
.enter()
.append("circle")
.attr("class", `dot`)
.attr("cx", d => xScale(d.month))
.attr("cy", d => yScale(d.X2t))
.attr("r", radius)
.attr("fill", d => colorScale(d.date))
.attr("stroke", "none")
function tick() {
d3.selectAll('.dot')
.attr("cx", (d) => { return d.x})
.attr("cy", (d) => d.y);
}
let simulation = d3.forceSimulation(data)
.force("x", d3.forceX((d) => xScale(d.month)).strength(1))
.force("y", d3.forceY((d) => yScale(d.X2t)).strength(1))
.force("collide", d3.forceCollide(2))
.alphaDecay(0.07)
.alpha(1)
.on("tick", tick);
const lineGenerator = d3.line()
.x(d => xScale(d.month))
.y(d => yScale(d.X2t))
.curve(d3.curveNatural)
const data_23 = data.filter(d => d.date >= new Date("2023-06-01") && d.date < new Date("2024-01-01"));
addLine(data_23, "2023")
function addLine(df, year) {
g.append("path")
.datum(df)
.attr("id", `warmest-path-${year}`)
.attr("fill", "none")
.attr("stroke", "grey")
.attr("stroke-width", 1.5)
.attr("stroke-dasharray", "5,2")
.attr("d", lineGenerator)
g.append("text")
.attr("dy", -4)
.append("textPath")
.attr("xlink:href", `#warmest-path-${year}`)
.style("text-anchor","middle")
.attr("startOffset", "70%")
.style("font-size", 14)
.text(year);
g.selectAll(`circle.recent-${year}`)
.data(df)
.join('circle')
.attr("class", "recent")
.attr("cx", d => xScale(d.month))
.attr("cy", d => yScale(d.X2t))
.attr("stroke", "grey")
.attr("fill", "none")
.attr("stroke-width", 1.5)
.attr("stroke-dasharray", "1,2")
.attr("r", 5)
}
svg.append("text")
.text("Seven consecutive months of temperature records")
.style("font-size", 17)
.style("font-weight", "semibold")
.attr('x', margin.left/2)
.attr("y", 25)
svg.append("text")
.text("Monthly global average temperature by year")
.style("font-size", 13)
.style("fill", "grey")
.attr('x', margin.left/2)
.attr("y", 55)
const decadeSamples = Array.from(
d3.group(data, d => Math.floor(new Date(d.date).getFullYear() / 10) * 10).values()
).map(group => group[0]);
const decadeLabels = d3.range(1940, 2025, 10);
svg.selectAll("circle.legend-item")
.data(decadeSamples)
.join("circle")
.attr("class", "legend-item")
.attr("cx", (d,i)=> i * 30 + width/2 + 15)
.attr("cy", 50)
.attr("r", 3)
.style("fill", d => colorScale(d.date))
svg.selectAll("text.legend-label")
.data(decadeLabels)
.join("text")
.attr("class", "legend-label")
.attr("x", (d,i)=> i * 30 + width/2)
.attr("y", 70)
.style("font-size", 12)
.text((d,i) => i % 2 === 0 ? `${d}s` : "")
const x1 = xScale(6)-30, y1 = yScale(16.7);
const x2 = xScale(6), y2 = yScale(16.6);
const arrowRadius = 30;
const pathD = `M${x1},${y1} A${arrowRadius},${arrowRadius} 0 0,1 ${x2},${y2}`;
g.append("path")
.attr("d", pathD)
.attr("fill", "none")
.attr("stroke", "black")
.attr("stroke-width", 0.5)
return svg.node()
}