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()
}