accepted

Canonical pitch coordinates: attacker-perspective, x 0→100 own→opposition goal

Every event produced by an adapter is in canonical pitch coordinates before reaching a compute or render layer. x = 0 is the own goal, x = 100 is the opposition goal, y = 0 is attacker's right, y = 100 is attacker's left. No chart re-maps axes; all orientation is a render-time concern.

ShotMapPassMapPassFlowPassNetworkHeatmapTerritoryKDEFormationGoalMouthShotChart coordinate-systemcanonicalinvariants

Context

Football providers use incompatible coordinate systems:

  • Opta — bottom-to-top y, absolute coordinates where teams attack one direction per half.
  • StatsBomb — top-to-bottom y, attacker-perspective.
  • Wyscout / WhoScored — yet another variant.

Without a canonical normalisation, every chart would re-implement axis flipping — inconsistently. Each provider requires its own attack-direction rotation: Opta F24 absolute coordinates need both-axes rotation when a team attacks toward decreasing-x; Opta EG (event-grid) is attack-relative and passes through; StatsBomb needs y-inversion only.

Decision

Every adapter normalises to canonical attacker-perspective:

  • x ∈ [0, 100], 0 = own goal, 100 = opposition goal.
  • y ∈ [0, 100], 0 = attacker’s right touchline, 100 = attacker’s left touchline.

Downstream compute and render layers never flip axes. Charts render with a canonical pitch in mind; the attackingDirection prop on every chart component ("up" | "down" | "left" | "right") controls visual orientation at the render layer.

The attackingDirection prop replaces the older orientation + mirror props across every pitch-based component. Butterfly layouts (e.g. dual-team comparisons) use attackingDirection="left" vs "right" instead of CSS scaleX(-1), so text + glyphs don’t invert alongside the pitch.

Consequences

  • Every adapter (fromOpta, fromStatsBomb, etc.) carries the burden of normalisation. Invariants documented in docs/standards/coordinate-invariants.md.
  • Do not hand-convert provider data. Run raw events through the adapter. Fixtures that hand-convert must cite the provider rule they applied — e.g. Opta y is bottom-to-top, so raw y=0 is the south touchline, which is the attacker’s right when attacking east.
  • Renderers take attackingDirection — butterfly layouts don’t need CSS tricks, so labels stay readable.
  • Pitch-anchored charts are out-of-scope for axis-padding / domain-pad because the pitch IS the bound; shifting the frame has no data-semantic meaning.
← All decisions