clubs = FileAttachment("data/clubs.csv").csv({typed: true})
// deep-link support: ?team=<team_code> preselects that club
initialTeam = new URLSearchParams(window.location.search).get("team")
initialClub = clubs.find((d) => d.team_code === initialTeam) ?? clubs[0]
viewof club = Inputs.select(clubs, {
label: "Club",
format: (d) => `${d.display_name} (${d.country})`,
value: initialClub,
sort: false,
unique: false
})Club Ranking History
Elo ranking history for every club. Pick a club to see its rating over time.
// Shared OJS theme — reads the CSS custom properties defined in styles.css :root
// so chart colors have a single source of truth (no copy-pasted hexes).
theme = {
const root = getComputedStyle(document.documentElement);
const v = (name, fallback) => (root.getPropertyValue(name).trim() || fallback);
return {
bg: v("--soccerverse-bg", "#383A3F"),
fg: v("--soccerverse-fg", "#F6B352"),
hl: v("--soccerverse-hl", "#F68657"),
// light→accent ramp used by the result-matrix heatmaps
cellRange: ["#fef3e4", v("--soccerverse-fg", "#F6B352")]
};
}// Responsive chart width: fills the content column (capped at 950) and
// updates on window resize so charts never overflow on small screens.
chartWidth = Generators.observe((notify) => {
const measure = () =>
notify(Math.min(950, (document.querySelector("main")?.clientWidth ?? 950) - 40));
measure();
addEventListener("resize", measure);
return () => removeEventListener("resize", measure);
})Plot.plot({
y: {
grid: false,
domain: d3.extent(elo, d => d.elo),
label: "↑ Elo"
},
x: {
label: "Year"
},
marks: [
Plot.line(elo, {
x: "date",
y: "elo",
tip: true,
title: (d) => `${d.elo} points`
})
],
style: {
background: theme.bg,
color: theme.fg,
fill: "#D3D3D3"
},
height: 600,
width: chartWidth,
marginLeft: 50,
marginRight: 50,
marginTop: 50,
marginBottom: 50
})