Heatmap defaults to fixed 12×8 bins; no adaptive grid
Zero-config Heatmap bins events into a fixed 12×8 grid. Adaptive schemes that vary bin count with data density were rejected because they break cross-match comparability — two heatmaps must be readable side by side without grid surprises.
Context
mplsoccer and similar libraries scale bin counts with data density (more
events → finer bins). That’s analytically sensible but editorially dangerous:
two heatmaps from different matches render with different grids, and visual
comparison becomes “which has more bins?” rather than “where was the action?”.
A publishable default must be predictable across matches.
Decision
gridX=12, gridY=8 hard-coded as the zero-config default. Consumers wanting
tactical layouts use zonePreset or pass explicit xEdges / yEdges arrays.
Sparse matches render with many empty cells — that’s correct, not a bug.
Finer-grain analytical work uses an explicit override, not an implicit
adaptive scheme.
Why 12×8
12 columns ≈ 8.75m each on a 105m pitch; 8 rows ≈ 8.5m each on a 68m pitch. Roughly square cells, aligned with football-zone intuition (thirds × sixths). Coarse enough to read at broadcast card sizes, fine enough to distinguish wing vs central activity.
Consequences
- Comparable across matches — cell index
(4, 3)is always the same physical region. - Sparse datasets look sparse.
autoCrop/ empty-state styling handles the degenerate case; zero counts don’t silently densify. - Tactical zones (18-zone, Juego de Posición) are available via
zonePreset="opta-18-zones"etc., not via agridResolution="fine"prop. - Territory and similar downstream charts lock to their own fixed grids and don’t inherit Heatmap’s.