svg = {
const width = 1200;
const height = 1700;
const n = data.length;
const radius = 1000 / 2;
const barPortion = 0.20;
const gapPortion = 1 - barPortion;
const monthGapFactor = 3;
const decGapFactor = 6;
const weights = data.map((d, i) => {
d.isLast = d.isLast === true || d.isLast === "True" || d.isLast === "true";
d.isFirst = d.isFirst === true || d.isFirst === "True" || d.isFirst === "true";
d.gapFactor = d.isLast ? (i === n - 1 ? decGapFactor : monthGapFactor) : 1;
return barPortion + gapPortion * d.gapFactor;
});
const totalWeight = d3.sum(weights);
const angleUnit = 2 * Math.PI / totalWeight;
const barAngle = barPortion * angleUnit;
let cumulative = 0;
const starts = new Array(n);
const mids = new Array(n);
for (let i = 0; i < n; ++i) {
starts[i] = cumulative;
mids[i] = cumulative + barAngle / 2;
cumulative += barAngle + gapPortion * data[i].gapFactor * angleUnit;
}
const bigGap = gapPortion * decGapFactor * angleUnit;
const gapCenterOrig = (starts[n - 1] + barAngle + bigGap / 2) % (2 * Math.PI);
const rotation = -gapCenterOrig;
const svg = d3.create("svg")
.attr("width", width)
.attr("height", height)
.style("background", "#000");
const leftMargin = 60;
const topMargin = 60;
svg.append("text")
.attr("x", leftMargin)
.attr("y", topMargin)
.attr("fill", "#fff")
.attr("font-family", "Overpass, sans-serif")
.attr("font-size", 80)
.attr("dominant-baseline", "hanging")
.text("2025");
svg.append("text")
.attr("x", leftMargin)
.attr("y", topMargin + 80 + 24)
.attr("fill", "#fff")
.attr("font-family", "Overpass, sans-serif")
.attr("font-size", 10)
.attr("font-weight", 700)
.attr("dominant-baseline", "hanging")
.text("SOLAR DAY DURATION");
const g = svg.append("g")
.attr("transform", `translate(${width / 2 - 15},${height / 2})`);
const y = d3.scaleLinear().domain([0, 100]).range([0, radius]);
const arc = d3.arc();
g.selectAll("path")
.data(data)
.join("path")
.attr("fill", "#fff")
.attr("d", (d, i) =>
arc({
outerRadius: radius,
innerRadius: radius - y(+d.sd_portion),
startAngle: starts[i] + rotation,
endAngle: starts[i] + rotation + barAngle
})
);
const digitRadius = radius + 12;
g.append("g")
.selectAll("text")
.data(data)
.join("text")
.text(d => d.date ?? d.Date ?? d.local_date)
.attr("font-size", 6)
.attr("fill", (d, i) => (i % 2 ? "#878787" : "#fff"))
.attr("text-anchor", "middle")
.attr("dominant-baseline", "middle")
.attr("transform", (d, i) =>
`rotate(${(mids[i] + rotation) * 180 / Math.PI - 90})
translate(${digitRadius},0)
rotate(90)`
);
const monthRadius = digitRadius + 14;
g.append("g")
.selectAll("text")
.data(
data.map((d, i) =>
d.isFirst ? {month: d.month, angle: starts[i] + rotation} : null
).filter(Boolean)
)
.join("text")
.text(d => d.month)
.attr("font-size", 11)
.attr("fill", "#fff")
.attr("transform", d =>
`rotate(${d.angle * 180 / Math.PI - 90})
translate(${monthRadius},0)
rotate(90)`
);
const defs = svg.append("defs");
defs.append("marker")
.attr("id", "arrowHead")
.attr("viewBox", "0 0 10 10")
.attr("refX", 5)
.attr("refY", 5)
.attr("markerWidth", 5)
.attr("markerHeight", 5)
.attr("orient", "auto-start-reverse")
.append("path")
.attr("d", "M 0 0 L 10 5 L 0 10 Z")
.attr("fill", "#fff");
g.append("circle")
.attr("r", 3)
.attr("fill", "#fff");
const arrowHeadLen = 10;
const lineLen = radius - arrowHeadLen;
const lowerLen = lineLen * 0.67;
const upperLen = lineLen - lowerLen;
g.append("line")
.attr("x1", 0).attr("y1", 0)
.attr("x2", 0).attr("y2", -lowerLen)
.attr("stroke", "#fff")
.attr("stroke-width", 2)
.attr("stroke-dasharray", "6 6");
g.append("line")
.attr("x1", 0).attr("y1", -lowerLen)
.attr("x2", 0).attr("y2", -(lowerLen + upperLen))
.attr("stroke", "#fff")
.attr("stroke-width", 2)
.attr("marker-end", "url(#arrowHead)");
g.append("text")
.text("number of hours (one day)")
.attr("font-size", 8)
.attr("font-family", "Overpass, sans-serif")
.attr("font-weight", "bold")
.attr("fill", "#fff")
.attr("text-anchor", "middle")
.attr("transform",
`translate(14, ${-lowerLen / 2}) rotate(90)`);
g.append("text")
.attr("x", 0).attr("y", 14)
.attr("fill", "#fff")
.attr("font-size", 8)
.attr("text-anchor", "middle")
.attr("dominant-baseline", "hanging")
.text("00:00");
g.append("text")
.attr("x", 0).attr("y", -(radius + 5))
.attr("fill", "#fff")
.attr("font-size", 8)
.attr("text-anchor", "middle")
.text("24:00");
return svg.node();
}