Public
Edited
May 23
Insert cell
Insert cell
weeklyThickness = ({
"NFL": [23.0, 23.0, 23.0, 23.0, 23.0, 23.0, 23.0, 23.0, 23.0, 23.0, 23.0, 23.0, 23.0, 23.0, 23.0, 23.0, 23.0, 23.0, 31.1, 35.7, 40.4, 45.0],
"FBS": [16.4, 16.4, 16.4, 16.4, 16.4, 16.4, 16.4, 16.4, 16.4, 16.4, 16.4, 16.4, 16.4, 25.2, 31.5, 37.7, 45.0],
"NBA": [18.9, 18.9, 18.9, 18.9, 18.9, 18.9, 18.9, 18.9, 18.9, 18.9, 18.9, 18.9, 18.9, 18.9, 18.9, 18.9, 18.9, 18.9, 18.9, 18.9, 18.9, 18.9, 18.9, 18.9, 18.9, 18.9, 27.7, 30.5, 33.2, 35.9, 39.5, 42.3, 45.0],
"PGA": [25.0, 25.0, 25.0, 25.0, 25.0, 25.0, 25.0, 45.0, 25.0, 25.0, 25.0, 25.0, 25.0, 25.0, 25.0, 45.0, 25.0, 25.0, 25.0, 25.0, 25.0, 25.0, 25.0, 45.0, 25.0, 25.0, 25.0, 25.0, 25.0, 25.0, 25.0, 45.0, 25.0, 25.0],
"MLB": [24.0, 24.0, 24.0, 24.0, 24.0, 24.0, 24.0, 24.0, 24.0, 24.0, 24.0, 24.0, 24.0, 24.0, 24.0, 24.0, 24.0, 24.0, 24.0, 24.0, 24.0, 24.0, 24.0, 24.0, 24.0, 24.0, 30.0, 34.0, 37.0, 41.0, 45.0],
"NHL": [19.4, 19.4, 19.4, 19.4, 19.4, 19.4, 19.4, 19.4, 19.4, 19.4, 19.4, 19.4, 19.4, 19.4, 19.4, 19.4, 19.4, 19.4, 19.4, 19.4, 19.4, 19.4, 19.4, 19.4, 19.4, 19.4, 29.0, 33.8, 37.0, 40.2, 45.0],
"EPL": [45.0, 45.0, 45.0, 45.0, 45.0, 45.0, 45.0, 45.0, 45.0, 45.0, 45.0, 45.0, 45.0, 45.0, 45.0, 45.0, 45.0, 45.0, 45.0, 45.0, 45.0, 45.0, 45.0, 45.0, 45.0, 45.0, 45.0, 45.0, 45.0, 45.0, 45.0, 45.0, 45.0, 45.0, 45.0, 45.0, 45.0, 45.0],
"CBB": [7.4, 7.4, 7.4, 7.4, 7.4, 7.4, 7.4, 7.4, 7.4, 7.4, 7.4, 7.4, 7.4, 7.4, 20.8, 29.2, 37.6, 45.0]
})

Insert cell
seasons = [
{ name: "NFL", sport: "NFL", start: "2024-09-05", end: "2025-02-09", color: "#00aaff" },
{ name: "College Football", sport: "FBS", start: "2024-08-27", end: "2025-01-06", color: "#ffaa00" },
{ name: "NBA", sport: "NBA", start: "2024-10-22", end: "2025-06-08", color: "#ff0033" },
{ name: "MLB", sport: "MLB", start: "2024-03-28", end: "2024-10-02", color: "#3399ff" },
{ name: "College Basketball", sport: "CBB", start: "2024-11-05", end: "2025-04-07", color: "#ff33cc" },
{ name: "Premier League", sport: "EPL", start: "2024-08-17", end: "2025-05-25", color: "#cc00ff" },
{ name: "NHL", sport: "NHL", start: "2024-10-08", end: "2025-06-15", color: "#66ff00" },
{ name: "PGA Tour", sport: "PGA", start: "2024-01-04", end: "2024-08-25", color: "#00ffcc" }
]

Insert cell
parseDate = d3.timeParse("%Y-%m-%d")




Insert cell
formatMonth = d3.timeFormat("%b")
Insert cell
weeksInYear = 52

Insert cell
anglePerWeek = (2 * Math.PI) / weeksInYear

Insert cell
Object.keys(weeklyThickness)

Insert cell
seasons.map(s => s.sport)

Insert cell
viewof radialChart = {
const svg = d3.create("svg")
.attr("width", 1000)
.attr("height", 1000)
.attr("viewBox", [-500, -500, 1000, 1000])
.style("font", "14px sans-serif");

const g = svg.append("g");

const centerHole = 80;
const monthRingOuter = centerHole + 30;

// Month ring
for (let i = 0; i < 12; i++) {
const angleStart = (i / 12) * 2 * Math.PI;
const angleEnd = ((i + 1) / 12) * 2 * Math.PI;
const midAngle = (angleStart + angleEnd) / 2;

g.append("path")
.attr("d", d3.arc()({
innerRadius: centerHole,
outerRadius: monthRingOuter,
startAngle: angleStart,
endAngle: angleEnd
}))
.attr("fill", i % 2 === 0 ? "#fff" : "#eee");

g.append("text")
.attr("transform", `rotate(${(midAngle * 180 / Math.PI - 90)}) translate(${(centerHole + monthRingOuter) / 2},0)`)
.attr("dy", "0.35em")
.style("text-anchor", "middle")
.style("fill", "#000")
.text(formatMonth(new Date(2024, i, 1)));
}

let currentRadius = monthRingOuter + 10;

for (const season of seasons) {
const { sport, start, color } = season;
const weeks = weeklyThickness[sport];
if (!weeks) {
console.warn("Missing weeklyThickness for sport:", sport);
continue;
}

const startDate = parseDate(start);
const startWeek = d3.utcWeek.count(new Date("2024-01-01"), startDate);

const inner = currentRadius;

for (let j = 0; j < weeks.length; j++) {
const thickness = weeks[j];
const outer = inner + thickness;
const angleStart = (startWeek + j) * anglePerWeek;
const angleEnd = (startWeek + j + 1) * anglePerWeek;

g.append("path")
.attr("d", d3.arc()({
innerRadius: inner,
outerRadius: outer,
startAngle: angleStart,
endAngle: angleEnd
}))
.attr("fill", color)
.attr("stroke", "#fff");

currentRadius = Math.max(currentRadius, outer); // Extend for next sport
}

currentRadius += 0; // No gap between sports
}

// League labels
const labelOffsets = {
"NFL": -140,
"FBS": -180,
"NBA": -215,
"MLB": -260,
"CBB": -295,
"EPL": -340,
"NHL": -375,
"PGA": -420
};

for (const sport in labelOffsets) {
g.append("text")
.attr("x", 0)
.attr("y", labelOffsets[sport])
.attr("text-anchor", "middle")
.attr("dominant-baseline", "middle")
.style("font-size", "14px")
.style("font-weight", "bold")
.text(sport);
}

// Gridlines
const gridGroup = svg.insert("g", ":first-child");

for (let i = 0; i < 12; i++) {
const angle = (i / 12) * 2 * Math.PI;
const x = Math.cos(angle) * currentRadius;
const y = Math.sin(angle) * currentRadius;

if (Math.abs(x) < 1e-4) continue;

gridGroup.append("line")
.attr("x1", 0)
.attr("y1", 0)
.attr("x2", x)
.attr("y2", y)
.attr("stroke", "#000");
}

return svg.node();
}

Insert cell

Purpose-built for displays of data

Observable is your go-to platform for exploring data and creating expressive data visualizations. Use reactive JavaScript notebooks for prototyping and a collaborative canvas for visual data exploration and dashboard creation.
Learn more