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", 1)
.attr("stroke-opacity", .4)
.attr("cy", d => -d.radius)
.style("transition", "transform 500ms ease");
svg.append("circle") //prevent select field tiks on grag
.attr("transform", `translate(${width / 2},${height / 2})`)
.attr('r', radius).attr('fill-opacity', 0)
const len = d => d.radius * 2 * Math.PI / d.range.length,
timeShiftRing = svg.append("g")
.attr('class', 'time-shift-rings')
.attr("transform", `translate(${width / 2},${height / 2})`)
.selectAll("g")
.data(fields)
.join("g")
.call(drag)
timeShiftRing.append("g")
.attr("transform", d => `rotate(${-90-len(d)/d.radius*180/Math.PI/2+WTF[2]})`)
.append("circle")
.attr("stroke-width", dotRadius * 2 + 2)
.style('stroke-dasharray', d => `${len(d) - 5} 5`)
.attr("r", d => d.radius)
timeShiftRing.append("circle") // hover hot area
.attr("stroke-width", dotRadius * 3 + 3)
.attr("r", d => d.radius)
.style('stroke-opacity', 0)
// timeShiftRing.append("line")
// .attr("stroke", "currentColor")
// .attr('y1', d => -d.radius + dotRadius)
timeShiftRing.append('path')
.attr('fill', 'gray')
.attr("transform", d => `translate(0, ${-d.radius-dotRadius+8}) scale(2)`)
.attr('d', 'M 0 0 L -2 -3 L 2 -3 Z')
const centerControl = svg.append("g")
.attr("transform", `translate(${width / 2},${height / 2})`)
const timeScaleRate = centerControl.append("text")
.attr('transform', `translate(0, -${fields[0].radius / 3})`)
const centerDate = centerControl.append("text")
window.centerAngle = centerControl.append("text")
.attr('transform', `translate(0, ${fields[0].radius / 3})`)
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) {
const formatTime = d3.timeFormat("%d.%m.%Y %H:%M:%S")
timeScaleRate.text('Real time x1')
centerDate.text(formatTime(new Date(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()
}
}