Phase 3: data-story viz — StatsBomb shot map, xG race, pass networks
- buildStatsbombViz.ts: processes the 2022 WC Final (Argentina 3-3 France) from StatsBomb open data → shots+xG, cumulative xG race, starting-XI pass networks, totals, shootout result. Output committed (11KB) to avoid build-time fetch - Custom SVG viz (no chart lib): reusable Pitch (StatsBomb 120x80 coords), ShotMap (xG-sized dots, teams attacking opposite goals), XgRace (stepped cumulative xG with goal markers, d3-scale), PassNetwork (nodes ∝ involvement, links ∝ pass volume) - StoryPage: narrative with hero scoreline, by-the-numbers bars, and the three visualizations with explanatory copy - Verified in browser: all three render correctly, no console errors Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -117,6 +117,47 @@ export interface ModelSnapshot {
|
||||
history: OddsHistoryPoint[];
|
||||
}
|
||||
|
||||
// ---- data-story viz (StatsBomb open data, a fixed historical match) ----
|
||||
|
||||
export interface VizShot {
|
||||
team: string;
|
||||
player: string;
|
||||
minute: number;
|
||||
/** StatsBomb pitch coords: x 0–120 (attacking toward 120), y 0–80. */
|
||||
x: number;
|
||||
y: number;
|
||||
xg: number;
|
||||
outcome: string;
|
||||
goal: boolean;
|
||||
}
|
||||
|
||||
export interface XgPoint { minute: number; cum: number; }
|
||||
|
||||
export interface PassNode { player: string; x: number; y: number; passes: number; }
|
||||
export interface PassEdge { a: string; b: string; count: number; }
|
||||
export interface PassNetwork { players: PassNode[]; edges: PassEdge[]; }
|
||||
|
||||
export interface TeamTotals { xg: number; shots: number; goals: number; passes: number; passAccuracy: number; }
|
||||
|
||||
export interface StoryViz {
|
||||
matchId: number;
|
||||
competition: string;
|
||||
stage: string;
|
||||
date: string;
|
||||
home: string;
|
||||
away: string;
|
||||
homeScore: number;
|
||||
awayScore: number;
|
||||
/** Final result line, e.g. "Argentina won 4–2 on penalties". */
|
||||
result: string;
|
||||
pitch: { length: number; width: number };
|
||||
shots: VizShot[];
|
||||
goals: { team: string; player: string; minute: number; xg: number }[];
|
||||
xgRace: { home: XgPoint[]; away: XgPoint[] };
|
||||
passNetwork: { home: PassNetwork; away: PassNetwork };
|
||||
totals: { home: TeamTotals; away: TeamTotals };
|
||||
}
|
||||
|
||||
// ---- WebSocket protocol ----
|
||||
// The snapshot is small (~104 fixtures), so the server simply re-pushes the full
|
||||
// snapshot on every change — no diffing, no client-side merge races. Predictions
|
||||
|
||||
Reference in New Issue
Block a user