chart = {
const svg = d3.create("svg")
.attr("viewBox", [0, 0, width, height])
.attr("text-anchor", "middle")
.style("display", "block")
.style("font", "500 14px var(--sans-serif)");
const field = svg.append("g")
.attr("transform", `translate(${width / 2},${height / 2})`)
.selectAll("g")
.data(fields)
.join("g");
field.append("circle")
.attr("fill", "none")
.attr("stroke", "currentColor")
.attr("stroke-width", 1.5)
.attr("r", d => d.radius);
const fieldTick = field.selectAll("g")
.data(d => {
const date = d.interval(new Date(2000, 0, 1));
d.range = d.subinterval.range(date, d.interval.offset(date, 1));
return d.range.map(t => ({time: t, field: d}));
})
.join("g")
.attr("class", "field-tick")
.attr("transform", (d, i) => {
const angle = i / d.field.range.length * 2 * Math.PI - Math.PI / 2;
return `translate(${Math.cos(angle) * d.field.radius},${Math.sin(angle) * d.field.radius})`;
});
const fieldCircle = fieldTick.append("circle")
.attr("r", dotRadius)
.attr("fill", "white")
.style("color", (d, i) => color(i / d.field.range.length * 2 * Math.PI))
.style("transition", "fill 750ms ease-out");
fieldTick.append("text")
.attr("dy", "0.35em")
.attr("fill", "#222")
.text(d => d.field.format(d.time).slice(0, 2));
const fieldFocus = field.append("circle")
.attr("r", dotRadius)
.attr("fill", "none")
.attr("stroke", "#000")
.attr("stroke-width", 3)
.attr("cy", d => -d.radius)
.style("transition", "transform 500ms ease");
yield update(Math.floor((Date.now() + 1) / 1000) * 1000);
while (true) {
const then = Math.ceil((Date.now() + 1) / 1000) * 1000;
yield Promises.when(then, then).then(update);
}
function update(then) {
for (const d of fields) {
const start = d.interval(then);
const index = d.subinterval.count(start, then);
d.cycle = (d.cycle || 0) + (index < d.index);
d.index = index;
}
fieldCircle.attr("fill", (d, i) => i === d.field.index ? "currentColor" : "white");
fieldFocus.attr("transform", d => `rotate(${(d.index / d.range.length + d.cycle) * 360})`);
return svg.node();
}
}