Public
Edited
May 2
1 fork
1 star
Insert cell
Insert cell
Insert cell
monarch_mariage = FileAttachment("monarch_mariage.png").image()
Insert cell
Insert cell
Insert cell
Insert cell
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"); //потрібно, якщо не в Observable



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 = {};

//використовуємо індекс, бо перші літери від місяцв не уникальні і у випадку використання їх для шкали, шкала матиме менше значень ніж 12 місяців
monthNames.forEach((month, index) => {
dataByMonth[index] = monthNames[index - 1];
})

// Якщо потрібно додати до набору показники за жовтень 2023 - червень 2024
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];
// d.date = parseDate(d.date) //потрібно, якщо не в Observable
});

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;

// Створюємо шкалу Y для температури
const yScale = d3.scaleLinear()
.domain([minTemp, maxTemp])
.range([innerHeight, 0])
.nice();

// Створюємо шкалу X для місяців
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);
}

//force simulation
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
.alpha(1) //високе значення означає більший рух, низьке — менший.
.on("tick", tick);

//Лінія температурного рекорду
//line generator
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}`) //тут path згідно з яким треба вигнути лінію
.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)
}


//TITLE
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)

//LEGEND
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` : "")

//ANNOTATION
const x1 = xScale(6)-30, y1 = yScale(16.7);
const x2 = xScale(6), y2 = yScale(16.6);
const arrowRadius = 30;

// A - дуга (arc) / rx,ry = arrowRadius / x-axis-rotation = 0 / large-arc-flag = 0/ 1=sweep-flag
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()
}
Insert cell
Insert cell
chart2 = {
const svg = d3.create("svg")
.attr("width", 100)
.attr("height", 150)

// Початок і кінець дуги
const x1 = 30, y1 = 70;
const x2 = 70, y2 = 70;
const arrowRadius = 30;

// SVG стрілка з дугою (A - arc/ rx,ry = arrowRadius / x-axis-rotation = 0 / large-arc-flag = 0/ 1=sweep-flag )
const pathD = `M${x1},${y1} A${arrowRadius},${arrowRadius} 0 0,0 ${x2},${y2}`;

// Дуга зі стрілкою
svg.append("path")
.attr("d", pathD)
.attr("fill", "none")
.attr("stroke", "black")
.attr("stroke-width", 0.5)
return svg.node()

}
Insert cell
//Окремо показники за жовтень 2023 - червень 2024
update = [
{ month: 10, date: new Date("2023-10-01"), X2t: 15.2, monthString: "O"},
{ month: 11, date: new Date("2023-11-01"), X2t: 14.2, monthString: "N"},
{ month: 12, date: new Date("2023-12-01"), X2t: 13.5, monthString: "D"},
{ month: 1, date: new Date("2024-01-01"), X2t: 13.1, monthString: "J"},
{ month: 2, date: new Date("2024-02-01"), X2t: 13.6, monthString: "F"},
{ month: 3, date: new Date("2024-03-01"), X2t: 14.1, monthString: "M"},
{ month: 4, date: new Date("2024-04-01"), X2t: 15.0, monthString: "A"},
{ month: 5, date: new Date("2024-05-01"), X2t: 16.0, monthString: "M"},
{ month: 6, date: new Date("2024-06-01"), X2t: 16.6, monthString: "J"},
]

Insert cell
<style>
path.domain {
display: none
}

#text-path {
fill:none;
stroke: grey;
stroke-width: 1.5;
stroke-dasharray: 1,2;
}
</style>
Insert cell
Insert cell
Insert cell
era5_monthly_temp_global_1940-2023@2.csv
Type Table, then Shift-Enter. Ctrl-space for more options.

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