ShotMap shape + fill grammar is preset-owned; Opta is outcome-first, StatsBomb is xG-first
`preset="opta"` locks markers to circles with fill-vs-hollow encoding outcome (goal vs non-goal). `preset="statsbomb"` uses shapes keyed to context/body-part (hexagon foot, circle header, square set-piece) with fill colour driven by the xG colour ramp. The two presets intentionally produce different charts from the same data model.
Context
Opta is outcome-first: the editorial question is “did it go in?”, and the
dominant visual read is a filled-for-goal / hollow-for-shot binary. StatsBomb
is xG-first: the editorial question is “how good was the chance?”, with context
(set-piece, body-part) carrying almost as much signal as xG itself. Forcing one
grammar across both providers loses interpretability on whichever convention it
doesn’t match, and hand-coding a custom markers.shape + markers.fill pair
for every consumer who changes provider is friction we don’t want.
Decision
Each preset owns its shape + fill grammar.
- Opta — always circles.
fill = outcome ? accent : transparent;strokeWidthlifts for goals. No xG colour ramp (Opta’s xG field is sparser and not the editorial focus). Legend is a shape-outline pair, not a gradient. - StatsBomb — shapes switch on
context/bodyPart: hexagon (foot), circle (header), square (set-piece), triangle (corner), diamond (other).fill = interpolate(colorScale, clampProbability(xg)). Legend is a colour gradient bar plus a shape-glyph key. - Missing xG under StatsBomb preset — falls through to an outcome-colour fallback and hides the gradient legend, so sparse datasets still render.
Consequences
- Mixing Opta and StatsBomb shots in one chart is not supported out of the box.
Consumers pick a preset or override both
markers.shapeandmarkers.fill. - Legend composition is preset-dependent: shape-only under Opta, gradient + glyph key under StatsBomb. The compute model surfaces both pieces so renderers don’t reinvent the split.
- Shape legend is omitted entirely when the dataset collapses to a single shape — no “key” for a one-entry category.
- GoalMouthShotChart keeps its own diamond-for-goal / circle-for-shot grammar independent of preset; it’s an outcome-focused view by construction.