chart = {
const svg = d3.create('svg')
.attr('viewBox', [-width/2, -height/2, width, height]);
svg.selectAll('circle')
.data(decades)
.join('circle')
.attr('r', (d) => timeScale(d))
.attr('fill', (_, i) => i % 2 == 0 ? 'whitesmoke' : 'white')
.attr('stroke-width', 0);
svg.selectAll('.label-year')
.data(decades)
.join('text')
.attr('class', 'label-year')
.attr('x', d => timeScale(d) + ringWidth / 2)
.attr('y', 0)
.attr('text-anchor', 'middle')
.attr('alignment-baseline', 'middle')
.attr('font-size', '12px')
.text((d) => d);
const g = svg.selectAll('g')
.data(nested)
.enter()
.append('g')
.attr('transform', (_, i) => `rotate(${(i * 360) / nested.length})`);
g.append('text')
.attr('x', 0)
.attr('y', timeScale.range()[1] + ringWidth + 10)
.attr('text-anchor', 'middle')
.attr('alignment-baseline', 'hanging')
.attr('font-size', '12px')
.text((d) => d.key);
g.selectAll('line')
.data((d) => d.values)
.join('line')
.attr('x1', 0)
.attr('x2', 0)
.attr('y1', (d) => timeScale(d.decade))
.attr('y2', (d) => timeScale(d.decade) + ringWidth + 5)
.attr('stroke-width', 3)
.attr('stroke', (d, i) => d.decade % 20 == 0 ? 'steelblue' : 'lightblue');
return svg.node();
}