Public
Edited
Jun 6
Insert cell
Insert cell
d19b7f836019175d41812f26338 = FileAttachment("d19b7f83601917.5d41812f26338.jpg").image()
Insert cell
sunrise_sunset_data2
Type Table, then Shift-Enter. Ctrl-space for more options.

Insert cell
chart2 = {

const width = 1000;
const height = width;
const innerRadius = 120;
const outerRadius = Math.min(width, height) * 0.4;
const opacity = 0.7;

const parseISO = d3.timeParse("%Y-%m-%d");
const parseHMS = d3.timeParse("%H:%M:%S");
const fmtDayMon = d3.timeFormat("%d-%m");

const dataWithExtraInfo = df.map(d => {
const dt = typeof d.date === "string" ? parseISO(d.date) : d.date;
const sr = parseHMS(d.sunrise);
const ss = parseHMS(d.sunset);

const minutes = (ss - sr) / 6e4;

return {
...d,
day : dt.getDate(),
month : dt.getMonth() + 1,
daylight_h : +(minutes / 60).toFixed(2),
month_name : d3.timeFormat("%B")(dt),
day_month: fmtDayMon(dt)
};
});


const svg = d3.create("svg")
.attr("width", width)
.attr("height", height)
.style("background-color", "black");

const g = svg.append("g").attr("transform", `translate(${width/2},${height/2})`)

//----------------------------------
// constants you can tweak quickly
//----------------------------------
//const margin = 28; // distance from the edges
const yearTxt = 2024; // or whatever variable you already have
const caption = "daylight duration";
const bigFont = 84; // px
const smallFont= 16; // px
const lineLen = 220;
const margin = -450;


const grp = g.append("g")
.attr("class", `corner-topleft`)
.attr("transform", `translate(${margin},${margin})`)
.attr("text-anchor", "start") // text alignment after rotation
.attr("fill", "white");

grp.append("text")
.text(yearTxt)
.attr("font-size", bigFont)
.attr("dominant-baseline", "hanging");

grp.append("text")
.text(caption)
.attr("y", bigFont + 6)
.attr("font-size", smallFont)
.attr("dominant-baseline", "hanging");

const arc = d3.arc()
.innerRadius(outerRadius + 10)
.outerRadius(outerRadius + 15);
const monthsMap = d3.flatRollup(dataWithExtraInfo,
v => v.length,
d => d.month,
d => d.month_name )

const months = Array.from(monthsMap, ([ number, name, freq]) => ({ number, name, freq }));

const pie = d3.pie().sort(null).value((d) => d.freq).padAngle(0.02);

const monthsArcs = pie(months);

const path = g.selectAll("path.month")
.data(monthsArcs)
.join("path")
.attr("id", d => d.data.name )
.attr("class", "month" )
.attr("d", arc)
months.forEach(d => {
g.append("text")
.attr("class", "months")
.append("textPath")
.attr("href", `#${d.name}`)
.style("text-anchor","start")
.style("font-size", 15)
.attr("fill", "white")
.text(d.name);
})

const daysAngles = {};
console.log('dataWithExtraInfo', dataWithExtraInfo)

monthsArcs.forEach(arc => {
const monthName = arc.data.name;
const monthDays = dataWithExtraInfo.filter(d => d.month_name === monthName);
if (monthDays.length > 0) {
const angleScale = d3.scalePoint()
.domain(monthDays.map(d => d.day_month))
.range([arc.startAngle + 0.015, arc.endAngle - 0.015]);
console.log('angles', arc.startAngle + 0.025, arc.endAngle - 0.025)
monthDays.forEach(row => {
daysAngles[row.day_month] = Array.from([angleScale(row.day_month), row.daylight_h, row.day, row.day_month]);
});
}
});
const daysAnglesArray = Object.values(daysAngles)

const y = d3.scaleLinear()
.domain([0, 24])
.range([0, outerRadius]);
g.selectAll(`line`)
.data(daysAnglesArray)
.join("line")
.attr("x1", d => Math.sin(d[0]) * (y(24 - d[1])))
.attr("y1", d => -Math.cos(d[0]) * (y(24 - d[1])))
.attr("x2", d => Math.sin(d[0]) * outerRadius)
.attr("y2", d => -Math.cos(d[0]) * outerRadius)
.attr("stroke", 'white')
.attr("stroke-width", 1)
.attr("opacity", opacity)

const labelOffset = 6;

g.selectAll(`text2`)
.data(daysAnglesArray)
.join("text")
.attr("class", "day")
.attr("x", d => Math.sin(d[0]) * (outerRadius + labelOffset))
.attr("y", d => -Math.cos(d[0]) * (outerRadius + labelOffset))
.attr("dy", "0.35em")
.attr("text-anchor", "middle")
.attr("transform", d => {
const a = d[0] * 180 / Math.PI;
return `rotate(${a}, ${Math.sin(d[0]) * (outerRadius + labelOffset)}, ${
-Math.cos(d[0]) * (outerRadius + labelOffset)})`;
})
.style("font-size", 6)
.style("fill", "white")
.text(d => d[2]);

const defs = svg.append("defs");

defs.append("marker")
.attr("id", "arrow")
.attr("viewBox", "0 -5 10 10")
.attr("refX", 10)
.attr("refY", 0)
.attr("markerWidth", 6)
.attr("markerHeight", 6)
.attr("orient", "auto")
.append("path")
.attr("d", "M0,-5L10,0L0,5")
.attr("fill", "white");

const x2 = Math.sin(0) * outerRadius
const y2 = -Math.cos(0) * outerRadius

g.append("path")
.attr("id", "hours-path") // target for textPath
.attr("d", `M0,0 L${x2},${y2}`) // same geometry as the old line
.attr("stroke", "white")
.attr("stroke-width", 1)
.attr("stroke-dasharray", "4 4")
.attr("marker-end", "url(#arrow)")
.attr("opacity", opacity);


g.append("text")
.attr("dy", -10)
.style("fill", "white")
.style("font-size", 11)
.append("textPath")
.attr("href", "#hours-path")
.attr("startOffset", "30%")
.attr("text-anchor", "middle")
.text("hours in one day (24h)");




return svg.node()
}
Insert cell
sunrise_sunset_data-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