mplsoccer → PitchKit
The cheatsheet — every common mplsoccer call and its PitchKit equivalent.
PitchKit is deliberately "mplsoccer for the web": the concepts map closely, so most of migrating is translating syntax, not relearning ideas. The real shifts are structural — and there are only three:
- A React tree instead of a matplotlib figure. No
fig, ax = pitch.draw(); the pitch is a component and marks are its children. - Accessors instead of column arrays. mplsoccer takes parallel arrays
(
pitch.scatter(df.x, df.y)); PitchKit takes your records once plus a function per visual property (<Scatter data={shots} x={(s) => s.x} />). - CSS instead of kwargs for colour.
pitch_color=/line_color=become the--pitch-*variables; per-mark colour stays inline via accessor props.
Pitch setup
| mplsoccer | PitchKit |
|---|---|
Pitch(pitch_type="statsbomb") | <Pitch type="statsbomb"> |
VerticalPitch(...) | <VerticalPitch type="..."> |
Pitch(half=True) | crop={cropForHalf(getPitchDimensions("statsbomb"))} |
Pitch(pitch_color="grass", stripe=True) | --pitch-surface variable + appearance={{ stripes: true }} |
Pitch(line_color="white", linewidth=2) | --pitch-lines / --pitch-line-width variables |
Pitch(goal_type="box") | appearance={{ goalType: "box" }} |
Pitch(line_zorder=2) | appearance={{ linesOnTop: true }} |
Pitch(pad_left=10, ...) | padding={{ left: 10, ... }} (pixels) |
fig, ax = pitch.draw(figsize=(8, 5)) | Not needed — responsive by default; width/height props to fix pixels |
Plotting
| mplsoccer | PitchKit |
|---|---|
pitch.scatter(x, y, s=size, ax=ax) | <Scatter data={d} x={...} y={...} r={...} /> |
pitch.arrows(xstart, ystart, xend, yend) | <Arrows data={d} x={...} y={...} x2={...} y2={...} /> |
pitch.lines(..., comet=True, transparent=True) | <Comet data={d} ... gradient /> |
pitch.annotate(text, xy=(x, y)) | <Annotate data={d} x={...} y={...} label={...} /> |
pitch.bin_statistic(...) + pitch.heatmap(...) | <Heatmap data={d} x={...} y={...} binsX={...} binsY={...} weight={...} /> |
pitch.bin_statistic_positional(...) + pitch.heatmap_positional(...) | <PositionalHeatmap data={d} x={...} y={...} layout="full" /> |
pitch.hexbin(x, y, gridsize=...) | <Hexbin data={d} x={...} y={...} binsX={...} /> |
pitch.kdeplot(x, y, bw_adjust=...) | <KDE data={d} x={...} y={...} bandwidth={...} /> |
pitch.flow(xstart, ystart, xend, yend, bins=...) | <Flow data={d} ... binsX={...} binsY={...} /> |
pitch.polygon(verts) | <Polygon data={d} points={...} /> |
pitch.convexhull(x, y) + pitch.polygon | <ConvexHull data={d} x={...} y={...} /> |
pitch.voronoi(x, y, teams) | <Voronoi data={d} x={...} y={...} fill={(p) => teamColor(p)} /> |
pitch.goal_angle(x, y) | <GoalAngle data={d} x={...} y={...} /> |
Binning for <Heatmap>/<Flow> happens inside the component — there's no separate
bin_statistic step to manage.
Utilities
| mplsoccer | PitchKit |
|---|---|
Standardizer(pitch_from="opta", pitch_to="statsbomb") | createStandardizeTransform(getPitchDimensions("opta"), getPitchDimensions("statsbomb")) |
| Pitch dimension constants | getPitchDimensions(type) / PITCH_DIMENSIONS |
FontManager, custom fonts | Plain CSS — pitches inherit your page's fonts |
One caveat on standardizing: PitchKit's current transform is a uniform extent mapping.
mplsoccer's Standardizer interpolates between pitch markings as control points, so
marking-boundary alignment across providers can differ slightly — exact for corners and
the centre spot, approximate at box edges.
Charts without a pitch
| mplsoccer | PitchKit |
|---|---|
Radar | <RadarChart> |
PyPizza | <PizzaChart> |
Like mplsoccer's, these draw the values you give them: percentiles and per-90s are yours to compute.
They are roots rather than layers, so there is no <Pitch> and no type prop. PitchKit also has two
charts mplsoccer doesn't: <RaceChart> and
<MomentumChart>.
Not ported (yet)
Bumpy charts are the one mplsoccer chart without a PitchKit equivalent. The roadmap issues track it. Everything in the tables above works today; see the gallery for finished equivalents of mplsoccer's own example charts.
A worked example
mplsoccer:
pitch = VerticalPitch(pitch_type="statsbomb", half=True)
fig, ax = pitch.draw()
pitch.scatter(df.x, df.y, s=df.xg * 500, c="#38bdf8", ax=ax)PitchKit:
const dims = getPitchDimensions("statsbomb");
<VerticalPitch type="statsbomb" crop={cropForHalf(dims)}>
<Scatter data={shots} x={(s) => s.x} y={(s) => s.y} r={(s) => 3 + s.xg * 9} fill="#38bdf8" />
</VerticalPitch>;