CometChart
Temporal scatter chart for how entities move through a two-metric space over time. Use it when the story is direction of change, not just a current snapshot. Hero fixture: approximate Premier League team-season npxG/npxGA values derived from public FBref-style summaries for 2022-23 and 2023-24.
Canonical behaviors.
Logo markers can replace circles without changing the chart model.
One entity over many seasons. Useful when the story is a single team or player arc.
Useful when lower values are better and should appear higher on the chart.
All teams visible with default labeling. This is the main overlap stress case.
When change is negligible, entities collapse down to dots rather than exaggerated trails.
No points, no fake trail geometry.
Stress case for mixed-script labels and long end annotations near the latest point.
ThemeProvider can restyle the chart without changing the grouped point model.
Composition patterns.
Use the shared note seam for sample framing instead of rebuilding chart-card copy around the component.
Shared concerns.
Choose CometChart
Use CometChart when the movement across time is the story. If you only need one snapshot, use a plain scatter chart.
Time Ordering
`timeKey` controls the trail order. Without it, array order becomes part of the visual contract.
Labels And Guides
Selective labels and median guides are usually enough. Over-labeling makes the trail story harder to read.
Accessibility
Markers and trail groups both expose tooltip content on hover. Marker buttons remain keyboard-focusable so the latest point is not the only inspectable state.
Export Posture
CometChart is intentionally outside the current stable `ExportFrameSpec` union. Treat it as a live React chart today rather than an export-safe chart card.
Responsive Behavior
CometChart is most legible in a wide analytical panel. On smaller widths the trail direction must survive before dense labeling or guide copy does.
Composition
The chart already owns trail, marker, and tooltip semantics. Shared methodology notes can stay inside the chart frame; broader narrative annotation should stay outside.
Width pressure.
A smaller width keeps the directional read, but should not try to keep every label and guide at full verbosity.
Wide layouts preserve more trail separation, guide labels, and collision-free annotations.
Best-practice examples.
Minimal usage
CometChart needs entity grouping plus two plotted metrics. Time is optional but usually the point of the chart.
import { CometChart } from "@withqwerty/campos-react";
export function TeamEvolution({ points }) {
return (
<CometChart
points={points}
entityKey="team"
xKey="npxgPer90"
yKey="npxgaPer90"
timeKey="season"
xLabel="npxG per 90"
yLabel="npxGA per 90"
/>
);
}
Guide and label variant
Guides and labels are first-class style surfaces, and trail or marker styling can be injected without changing the chart model.
import { CometChart } from "@withqwerty/campos-react";
export function TeamEvolutionGuides({ points }) {
return (
<CometChart
points={points}
entityKey="team"
xKey="npxgPer90"
yKey="npxgaPer90"
timeKey="season"
xLabel="npxG per 90"
yLabel="npxGA per 90"
labelStrategy="manual"
labelIds={["Arsenal", "Liverpool", "Brighton"]}
guides={[
{ axis: "x", value: "median", label: "Median npxG" },
{ axis: "y", value: "median", label: "Median npxGA" },
]}
lines={{
stroke: ({ entity }) => (entity.id === "Arsenal" ? "#dc2626" : "#2563eb"),
strokeDasharray: ({ entity }) => (entity.barelyMoved ? "3 3" : undefined),
}}
labels={{ fill: "#111827", connectorStroke: "#94a3b8" }}
guideStyle={{ stroke: "#94a3b8", opacity: 0.45 }}
/>
);
}
Public surface.
| Prop | Type | Default | Description |
|---|---|---|---|
points | readonly T[] | required | Array of data objects, each representing one entity at one time point. |
entityKey | keyof T & string | required | Field that groups rows into entities. |
xKey | keyof T & string | required | Field to plot on the x-axis. |
yKey | keyof T & string | required | Field to plot on the y-axis. |
timeKey | keyof T & string | — | Field used to sort points chronologically within each entity. |
xLabel | string | xKey | X-axis label. |
yLabel | string | yKey | Y-axis label. |
labelKey | keyof T & string | entityKey | Field used for entity labels and tooltip identity. |
invertX | boolean | false | Flip x-axis so higher values appear left. |
invertY | boolean | false | Flip y-axis so higher values appear lower. |
guides | readonly CometChartGuideInput[] | — | Vertical or horizontal guide lines using a number, mean, or median. |
showTimeLabels | boolean | false | Show time labels at each point. |
labelStrategy | "all" | "none" | "manual" | "all" | How entity labels are chosen. |
labelIds | readonly string[] | — | Subset of entity ids to label when labelStrategy is manual. |
lines | CometChartLinesStyle | — | First-class line styling surface for trail segments. |
markers | CometChartMarkersStyle | — | First-class point styling surface for comet markers. |
labels | CometChartLabelsStyle | — | First-class label styling surface for entity labels. |
guideStyle | CometChartGuidesStyle | — | First-class styling surface for rendered guide lines and labels. |
methodologyNotes | ChartMethodologyNotes | — | Shared chart-frame note seam for sample, eligibility, and methodological context. |
logoMap | Record<string, string> | — | Map entity ids to image URLs so markers render as logos instead of circles. Live React/browser seam only; CometChart is not yet in the stable export union. |
`logoMap` is supported in the live React component, but CometChart is still deferred from the stable `ExportFrameSpec` contract. The current export-safe matrix should not imply static/chart-card parity for this component yet.
Create a React component using Campos CometChart. Import CometChart from @withqwerty/campos-react, keep data shaping outside the component, show the smallest good usage first, then one variant with median guides and selective labels.