Heatmap
Uniform-grid positional heatmap. Use it when you want discrete bins you can inspect, compare, and label explicitly, including repeated analyst grids where each cell should still keep honest bins. The hero shows Arsenal touch events from a real WhoScored-derived fixture normalized through the adapter layer.
Interactive props.
Canonical behaviors.
Same pass-origin bins, but the scale and tooltip now report share-of-total instead of raw counts.
Higher-resolution binning is useful for dense event sets, but it becomes noisy more quickly.
Named tactical-zone presets give Heatmap a benchmark-recognizable positional layout without dropping to raw edge arrays.
Heatmap now has an honest compact-cell seam: hide the scale bar, keep the grid tactical or coarse, and map repeated horizontal cells to attackingDirection='right'.
Attacking-half crop proves that Heatmap still works as a focused inspection surface rather than only a full-pitch chart.
Single-hue scales are useful when the story is a defensive action footprint rather than possession territory.
Heatmap supports the static export pipeline. Hover UI disappears, but the binned surface and scale remain deterministic.
No overlay and no fake color cells when nothing is plottable.
Shared concerns.
Choose Heatmap
Use Heatmap when discrete bins matter. Use KDE for a smoothed estimate,
and use Territory when the chart should collapse into quick editorial
zones.
Interaction
Hover and focus operate at the cell level. valueMode changes tooltip
and scale semantics without changing the relative cell ordering.
Themeability
Pitch theming comes from Stadia. Dark sequential ramps automatically force light
pitch lines unless autoPitchLines is disabled.
Export
Heatmap is supported by the Phase 1 static export path. Use it when the hover state is not essential and the chart needs a deterministic snapshot.
Composition
Heatmap already owns its scale and tooltip. Wrap it in page layout chrome if needed, but do not rebuild those internals outside the component.
Zone Presets
Use zonePreset when the chart should align to shared tactical layouts
such as 18-zone or 20-zone positional bins. Use explicit xEdges and
yEdges only when you need a bespoke geometry.
Normal, small, smallest.
Pitch-based charts scale fluidly to whatever width the parent gives them. The 'smallest' cell is sized for a 5×4 small-multiples grid (~140px wide). Vertical orientation only — horizontal pitches are too wide for small-multiples.
Best-practice examples.
Minimal usage
Use Heatmap when you need discrete, inspectable bins rather than a smoothed density estimate.
import { Heatmap } from "@withqwerty/campos-react";
type EventPoint = {
x: number | null;
y: number | null;
};
type Props = {
events: EventPoint[];
};
export function TeamHeatmap({ events }: Props) {
return <Heatmap events={events} metricLabel="Touches" />;
}
Analytical variant
Value mode and grid resolution are the main first-class customizations for a heatmap page.
import { Heatmap } from "@withqwerty/campos-react";
type EventPoint = {
x: number | null;
y: number | null;
};
type Props = {
events: EventPoint[];
};
export function TeamHeatmapAnalytical({ events }: Props) {
return (
<Heatmap
events={events}
metricLabel="Passes"
colorScale="viridis"
valueMode="share"
gridX={25}
gridY={16}
/>
);
}
Repeated-grid cell
For SmallMultiples or analyst grids, suppress the scale bar and keep the bins tactical or coarse enough to stay legible.
import { Heatmap } from "@withqwerty/campos-react";
type EventPoint = {
x: number | null;
y: number | null;
};
type Props = {
events: EventPoint[];
};
export function TeamHeatmapCell({ events }: Props) {
return (
<Heatmap
events={events}
zonePreset="5x3"
attackingDirection="right"
metricLabel="Pass origins"
showScaleBar={false}
framePadding={8}
/>
);
}
Public surface.
| Prop | Type | Default | Description |
|---|---|---|---|
events | readonly { x: number | null; y: number | null }[] | required | Array of positional events in canonical Campos pitch coordinates. |
gridX | number | 12 | Number of bins along the attacking axis. |
gridY | number | 8 | Number of bins across the pitch width. |
zonePreset | "3x3" | "5x3" | "18" | "20" | — | Shared named pitch-zone preset. Overrides gridX/gridY unless explicit xEdges/yEdges are supplied. |
metricLabel | string | "Events" | Scale-bar and tooltip label for what each event count represents. |
valueMode | "count" | "intensity" | "share" | "count" | Controls how tooltip and scale values are expressed. Color cells stay in the same order. |
showScaleBar | boolean | true | Toggles the post-plot scale bar. Set false for compact repeated-grid cells. |
colorScale | "magma" | "viridis" | "inferno" | "blues" | "greens" | "custom" | "magma" | Sequential color ramp for the cell fills. |
colorStops | ColorStop[] | — | Custom ramp stops when colorScale='custom'. |
attackingDirection | "up" | "down" | "left" | "right" | "right" | Controls which direction of play the pitch projects toward on screen. |
crop | "full" | "half" | "full" | Pitch crop. Half filters cells and events to the attacking half. |
pitchTheme | "primary" | "secondary" | "primary" | Delegated Stadia surface preset. |
pitchColors | PitchColors | — | Direct pitch color overrides for branded or editorial surfaces. |
autoPitchLines | boolean | true | Automatically switches pitch lines to white on dark ramps so markings stay visible. |
cells | HeatmapCellsStyle | — | First-class styling surface for rendered heatmap cells. |
Create a React component using Campos Heatmap. Import Heatmap from @withqwerty/campos-react, keep data parsing outside the component, show the smallest good usage first, then add one analytical variant and one compact repeated-grid cell with showScaleBar={false}.