PassNetwork
Aggregated team passing network. Use it when the story is structure, shape, and relationships between players rather than individual pass trajectories. Hero fixture: Arsenal at Manchester United (0-1, GW1, 2025-08-17).
Interactive props.
Canonical behaviors.
Callbacks can restyle nodes and edges from raw xT values without routing that logic through a chart mode prop.
One dense, analyst-facing network with thresholding, directionality, and xT-driven styling combined. This is the public benchmark view the helper pipeline should be able to feed cleanly.
Higher threshold and labels off produce a cleaner publishable network.
When direction matters, reversed edges stay distinct and render with arrowheads.
Portrait layout for tighter cards and scouting-style treatments.
Too few nodes to sustain a dense structure, but the chart still reads honestly.
PassNetwork is supported by the static export pipeline when the story needs a deterministic snapshot.
Pitch visible, honest copy, and no fake structure when nothing is plottable.
Composition patterns.
Two independent PassNetwork instances laid out beside each other. Useful when each team should keep its own full pitch and scale. Liverpool 4-2 Bournemouth.
These are composition helpers, not chart props. compressPassNetwork remaps coordinates into a half; combinePassNetworks concatenates the results for one shared-pitch chart.
Shared concerns.
Choose PassNetwork
Use PassNetwork when you care about aggregated structure. Use PassMap when the story is individual pass trajectories and completion choices.
Data Contract
PassNetwork expects pre-aggregated nodes and edges. Use helpers like inferRecipientsFromNextPass, aggregatePassNetwork, compressPassNetwork, and
combinePassNetworks upstream; the chart is not the place to do the aggregation
or network compression work.
Interaction
Hover and focus can expose ego-highlight and per-edge tooltips. That interaction is part of the chart contract, not page-specific glue.
Themeability
Pitch theming comes from Stadia via the shared floating controller. Accent styling and data-driven xT styling then sit on top of that surface.
Export And Recipes
Static export is supported. Shared-pitch comparison is also supported, but via helper functions and wrapper composition rather than new props on the chart itself.
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
PassNetwork expects pre-aggregated nodes and edges. The aggregation helper path should live right next to the chart import, not hidden elsewhere in the codebase.
import {
PassNetwork,
aggregatePassNetwork,
inferRecipientsFromNextPass,
} from "@withqwerty/campos-react";
import type { PassEvent } from "@withqwerty/campos-schema";
import type { PassNetworkEdge, PassNetworkNode } from "@withqwerty/campos-react";
type Props = {
passes: PassEvent[];
teamId: string;
};
export function TeamPassNetwork({ passes, teamId }: Props) {
const annotated = inferRecipientsFromNextPass(passes);
const { nodes, edges } = aggregatePassNetwork(annotated, { teamId });
return <PassNetwork nodes={nodes} edges={edges} />;
}
High-threshold variant
Threshold and style injection are the main first-class customizations for a publishable network.
import { PassNetwork } from "@withqwerty/campos-react";
import type { PassNetworkEdge, PassNetworkNode } from "@withqwerty/campos-react";
type Props = {
nodes: PassNetworkNode[];
edges: PassNetworkEdge[];
};
export function TeamPassNetworkHighThreshold({ nodes, edges }: Props) {
return (
<PassNetwork
nodes={nodes}
edges={edges}
minEdgePasses={8}
showLabels={false}
/>
);
}
Public surface.
| Prop | Type | Default | Description |
|---|---|---|---|
nodes | readonly PassNetworkNode[] | required | Pre-aggregated player nodes with average position, pass count, and optional xT. |
edges | readonly PassNetworkEdge[] | required | Player-pair edges with pass counts and optional xT. Reversed pairs merge automatically unless directed mode is on. |
minEdgePasses | number | 4 | Threshold below which edges are dropped from the visible network. |
showLabels | boolean | true | Whether node labels render inside the circles. |
nodeStyle | PassNetworkNodeStyle | — | First-class node style surface. Accepts constants, keyed map shorthands, or callbacks for fill, stroke, radius, label color, opacity, and shape. |
edgeStyle | PassNetworkEdgeStyle | — | First-class edge style surface. Accepts constants, keyed map shorthands, or callbacks for stroke, width, dash, opacity, and visibility. |
attackingDirection | "up" | "down" | "left" | "right" | "right" | Controls which direction of play the pitch projects toward on screen. |
directed | boolean | false | Keeps reversed edges distinct and renders directional arrowheads. |
collisionPadding | number | 0.5 | Minimum post-relaxation gap between node edges. |
egoHighlight | boolean | true | Dim unrelated nodes and edges when hovering or focusing a node. |
pitchTheme | "primary" | "secondary" | "primary" | Delegated Stadia surface preset. |
pitchColors | PitchColors | — | Direct pitch color overrides for branded or editorial surfaces. |
Create a React component using Campos PassNetwork. Import PassNetwork plus aggregatePassNetwork (and inferRecipientsFromNextPass when needed) from @withqwerty/campos-react. Show the smallest good usage first with the aggregation outside the chart, then show one editorial cut using a higher minEdgePasses threshold or xT styling.