Component

ShotMap

Half-pitch shot map with publishable defaults. The hero fixture uses 14 real StatsBomb open-data shots normalized through @withqwerty/campos-adapters, then rendered with the default Opta-style editorial encoding.

xG
0.05
0.15
0.3
0.5
0.8
Goal
Shot
Playground

Interactive props.

14 real normalized shots with xG and mixed outcomes.Style panel demonstrates the callback-first `markers` / `trajectories` API.Pitch theme comes from the floating controller.
xG
0.05
0.15
0.3
0.5
0.8
Goal
Shot
Stories

Canonical behaviors.

Analytical Preset

Callback-first marker styling can push the chart toward an analytical read without changing the shot payload.

xG0.001.00
Foot
Set piece
Corner
Body-Part Shapes

Same analytical preset, but the glyph system now distinguishes body part rather than shot context.

xG0.001.00
Foot
Set piece
Corner
No-xG Fallback

When xG is missing, marker size falls back cleanly and the chart drops the size scale without breaking the outcome read.

Goal
Shot
Static Export

Static export uses the dedicated SVG path with a fixed outline pitch treatment. Hover UI disappears, but the pitch and markers stay deterministic.

GoalShot
Empty State

Pitch drawn, honest copy, and no fake marks when nothing is plottable.

No shot data
Cross-cutting

Shared concerns.

Data Contract

ShotMap expects canonical Campos shots. Adapter ownership stays outside the component, so the page examples only cover rendering choices.

Shot target (endX / endY)

Optional fields on each Shot place the ball after the attempt in Campos pitch space (0–100). Adapters populate them when the raw feed includes an end location: StatsBomb uses shot.end_location; Opta and WhoScored use qualifiers 140/141 (same as pass end coordinates) when present; Wyscout uses the second positions[] point on shot events. Not every shot in every feed includes target data—lines only render when both coordinates are present.

Interaction

The live component owns hover and focus tooltips per shot marker. Static export uses ShotMapStaticSvg, which intentionally removes that UI layer.

Migration note

The built-in analytical default now uses colorScale="magma" instead of "turbo". If you need the earlier StatsBomb-style output, set colorScale="turbo" explicitly.

Themeability

Pitch theming comes from Stadia. Use the floating controller for the built-in surface presets, and use pitchColors only when the chart needs a custom palette.

Composition

ShotMap owns the chart card, optional header stats, scale, legend, and tooltip. Wrap it in layout chrome if needed, but do not rebuild its internal pitch semantics outside the component.

Responsive

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.

Shots14
Goals6
xG3.07
xG
0.05
0.15
0.3
0.5
0.8
Goal
Shot
Normal
Shots14
Goals6
xG3.07
xG
0.05
0.15
0.3
0.5
0.8
Goal
Shot
Small
Smallest (small multiples)
Usage

Best-practice examples.

Minimal usage

Keep provider parsing outside the component. Zero-config defaults already produce a publishable half-pitch shot map.

                    import { ShotMap } from "@withqwerty/campos-react";
import type { Shot } from "@withqwerty/campos-schema";

type Props = {
  shots: Shot[];
};

export function TeamShotMap({ shots }: Props) {
  return <ShotMap shots={shots} />;
}
                  
Analytical encoding

Use callback-first style props when the chart needs a different reading, but keep the data shape unchanged.

                    import { ShotMap } from "@withqwerty/campos-react";
import type { Shot } from "@withqwerty/campos-schema";

type Props = {
  shots: Shot[];
};

export function TeamShotMapAnalytical({ shots }: Props) {
  return (
    <ShotMap
      shots={shots}
      preset="statsbomb"
      markers={{
        fill: ({ shot }) => {
          const xg = shot.xg ?? 0;
          if (xg > 0.4) return "#dc2626";
          if (xg > 0.2) return "#2563eb";
          return "#16a34a";
        },
        shape: {
          by: ({ shot }) => shot.bodyPart ?? "other",
          values: {
            head: "triangle",
            other: "square",
          },
          fallback: "circle",
        },
      }}
      trajectories={{
        stroke: { by: ({ shot }) => shot.outcome, values: { goal: "#dc2626", blocked: "#2563eb" } },
        strokeDasharray: { by: ({ shot }) => shot.outcome, values: { blocked: "4 3" } },
      }}
    />
  );
}
                  
API

Public surface.

Prop Type Default Description
shots readonly Shot[] required Normalized shot data. Keep provider parsing outside the component and pass canonical Campos shots in.
preset "opta" | "statsbomb" "opta" High-level visual encoding preset. Opta is the editorial default; StatsBomb switches to analytical color/shape semantics.
colorScale "magma" | "cividis" | "turbo" | "coolwarm" "magma" xG color ramp used by the built-in analytical presets. Since the 2026-04 chart-library pass, the analytical default is magma instead of turbo; pass `colorScale="turbo"` to retain the earlier output.
markers ShotMapMarkersStyle Callback-first marker styling surface for fill, fillOpacity, stroke, strokeWidth, opacity, size, and shape. Supports constants, object maps, and callbacks.
showHeaderStats boolean false Summary row (shots, goals, total xG) above the chart. Off by default so the pitch stays primary in tight layouts.
showScaleBar boolean true Controls the analytical xG color bar when the built-in preset exposes one. Useful for compact small-multiple tiles and dense comparison grids.
showShotTrajectory boolean true Lines from shot origin to endX/endY when the shot payload includes a target position. Toggle off for a cleaner scatter or when end data is noisy.
trajectories ShotTrajectoryStyle Callback-first styling surface for shot target lines: show, stroke, strokeWidth, opacity, strokeDasharray, strokeLinecap. Defaults follow the pitch theme when stroke is omitted.
pitchTheme "primary" | "secondary" "primary" Delegated pitch surface preset from Stadia.
pitchColors PitchColors Direct pitch color overrides for branded or editorial treatments.
Use with AI
LLM Prompt
Create a React component using Campos ShotMap. Import ShotMap from @withqwerty/campos-react, keep provider parsing outside the component, show the smallest good usage first, then show one callback-first styling variant using markers and trajectories.
Pitch