@sportsdataverse/sdvplot 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/NOTICE.md +15 -0
- package/README.md +84 -8
- package/dist/chartjs-surface.d.ts +2 -2
- package/dist/chartjs-surface.js +2 -2
- package/dist/chartjs-surface.js.map +1 -1
- package/dist/chartjs.d.ts +32 -2
- package/dist/chartjs.js +7 -4
- package/dist/chartjs.js.map +1 -1
- package/dist/chunk-57WFSI4Q.js +128 -0
- package/dist/chunk-57WFSI4Q.js.map +1 -0
- package/dist/{chunk-KKH6ABMA.js → chunk-7ULOCCFL.js} +94 -4
- package/dist/chunk-7ULOCCFL.js.map +1 -0
- package/dist/chunk-AMXASQHT.js +120 -0
- package/dist/chunk-AMXASQHT.js.map +1 -0
- package/dist/chunk-B2MP37SR.js +175 -0
- package/dist/chunk-B2MP37SR.js.map +1 -0
- package/dist/{chunk-NLPH2VZT.js → chunk-DYMUVE6K.js} +4 -4
- package/dist/{chunk-NLPH2VZT.js.map → chunk-DYMUVE6K.js.map} +1 -1
- package/dist/{chunk-QY65DUEF.js → chunk-H2C3FMMA.js} +4 -10
- package/dist/chunk-H2C3FMMA.js.map +1 -0
- package/dist/{chunk-JJFTD4VB.js → chunk-J4P4HDBF.js} +4 -4
- package/dist/{chunk-JJFTD4VB.js.map → chunk-J4P4HDBF.js.map} +1 -1
- package/dist/chunk-M3XD2OSN.js +132 -0
- package/dist/chunk-M3XD2OSN.js.map +1 -0
- package/dist/{chunk-OKEE4AH6.js → chunk-OIILARXI.js} +3 -3
- package/dist/{chunk-OKEE4AH6.js.map → chunk-OIILARXI.js.map} +1 -1
- package/dist/chunk-RPVVJY74.js +170 -0
- package/dist/chunk-RPVVJY74.js.map +1 -0
- package/dist/chunk-UL5M6KDE.js +205 -0
- package/dist/chunk-UL5M6KDE.js.map +1 -0
- package/dist/chunk-WQT4IVLX.js +10 -0
- package/dist/chunk-WQT4IVLX.js.map +1 -0
- package/dist/{chunk-TAXO3VXH.js → chunk-ZAEQXFJM.js} +3 -3
- package/dist/{chunk-TAXO3VXH.js.map → chunk-ZAEQXFJM.js.map} +1 -1
- package/dist/cytoscape.d.ts +188 -0
- package/dist/cytoscape.js +219 -0
- package/dist/cytoscape.js.map +1 -0
- package/dist/d3.d.ts +3 -3
- package/dist/d3.js +6 -6
- package/dist/d3.js.map +1 -1
- package/dist/deckgl.d.ts +232 -0
- package/dist/deckgl.js +197 -0
- package/dist/deckgl.js.map +1 -0
- package/dist/echarts.js +5 -5
- package/dist/g2.d.ts +251 -0
- package/dist/g2.js +102 -0
- package/dist/g2.js.map +1 -0
- package/dist/index.d.ts +2 -1
- package/dist/index.js +4 -5
- package/dist/lightweight-charts.d.ts +217 -0
- package/dist/lightweight-charts.js +155 -0
- package/dist/lightweight-charts.js.map +1 -0
- package/dist/maplibre.d.ts +271 -0
- package/dist/maplibre.js +175 -0
- package/dist/maplibre.js.map +1 -0
- package/dist/mui.d.ts +338 -0
- package/dist/mui.js +184 -0
- package/dist/mui.js.map +1 -0
- package/dist/nivo.d.ts +193 -0
- package/dist/nivo.js +8 -0
- package/dist/nivo.js.map +1 -0
- package/dist/perspective.d.ts +198 -0
- package/dist/perspective.js +171 -0
- package/dist/perspective.js.map +1 -0
- package/dist/placement-UWkqUzpa.d.ts +32 -0
- package/dist/plot.d.ts +7 -5
- package/dist/plot.js +9 -8
- package/dist/plot.js.map +1 -1
- package/dist/plotly.js +5 -5
- package/dist/react.d.ts +279 -3
- package/dist/react.js +364 -6
- package/dist/react.js.map +1 -1
- package/dist/recharts.d.ts +162 -0
- package/dist/recharts.js +8 -0
- package/dist/recharts.js.map +1 -0
- package/dist/shadcn.d.ts +83 -0
- package/dist/shadcn.js +88 -0
- package/dist/shadcn.js.map +1 -0
- package/dist/shots.js +3 -2
- package/dist/shots.js.map +1 -1
- package/dist/surface-TR7YFJW3.js +4 -0
- package/dist/surface-TR7YFJW3.js.map +1 -0
- package/dist/surfaces-MIG7HOFU.js +6 -0
- package/dist/surfaces-MIG7HOFU.js.map +1 -0
- package/dist/surfaces-W-odWLpT.d.ts +47 -0
- package/dist/svelte/HeadshotMark.svelte +25 -0
- package/dist/svelte/HeadshotMark.svelte.d.ts +10 -0
- package/dist/svelte/HeadshotMark.svelte.d.ts.map +1 -0
- package/dist/svelte/LogoMark.svelte +25 -0
- package/dist/svelte/LogoMark.svelte.d.ts +10 -0
- package/dist/svelte/LogoMark.svelte.d.ts.map +1 -0
- package/dist/svelte/Marks.svelte +25 -0
- package/dist/svelte/Marks.svelte.d.ts +10 -0
- package/dist/svelte/Marks.svelte.d.ts.map +1 -0
- package/dist/svelte/Surface.svelte +29 -0
- package/dist/svelte/Surface.svelte.d.ts +9 -0
- package/dist/svelte/Surface.svelte.d.ts.map +1 -0
- package/dist/svelte/SurfaceBackdrop.svelte +26 -0
- package/dist/svelte/SurfaceBackdrop.svelte.d.ts +11 -0
- package/dist/svelte/SurfaceBackdrop.svelte.d.ts.map +1 -0
- package/dist/svelte/index.d.ts +13 -0
- package/dist/svelte/index.d.ts.map +1 -0
- package/dist/svelte/index.js +11 -0
- package/dist/svelte-core.d.ts +139 -0
- package/dist/svelte-core.js +123 -0
- package/dist/svelte-core.js.map +1 -0
- package/dist/tanstack.d.ts +240 -0
- package/dist/tanstack.js +110 -0
- package/dist/tanstack.js.map +1 -0
- package/dist/testing.js +4 -5
- package/dist/testing.js.map +1 -1
- package/dist/three.d.ts +223 -0
- package/dist/three.js +50 -0
- package/dist/three.js.map +1 -0
- package/dist/{tiers-B6pUBPcE.d.ts → tiers-BN4md-GB.d.ts} +2 -30
- package/dist/unovis.d.ts +127 -0
- package/dist/unovis.js +108 -0
- package/dist/unovis.js.map +1 -0
- package/dist/uplot.d.ts +120 -0
- package/dist/uplot.js +110 -0
- package/dist/uplot.js.map +1 -0
- package/dist/vega.js +5 -5
- package/dist/victory.d.ts +107 -0
- package/dist/victory.js +38 -0
- package/dist/victory.js.map +1 -0
- package/package.json +218 -6
- package/dist/chunk-KKH6ABMA.js.map +0 -1
- package/dist/chunk-QY65DUEF.js.map +0 -1
- package/dist/chunk-RMT2GMFH.js +0 -97
- package/dist/chunk-RMT2GMFH.js.map +0 -1
- package/dist/chunk-SEBO35DV.js +0 -76
- package/dist/chunk-SEBO35DV.js.map +0 -1
- package/dist/chunk-TDNBAEQB.js +0 -105
- package/dist/chunk-TDNBAEQB.js.map +0 -1
- package/dist/surfaces-ChaD9nei.d.ts +0 -38
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
import { R as Row, a as DrawnMark } from './_web-D7Aa5Rd8.js';
|
|
2
|
+
export { e as embedSources } from './_web-D7Aa5Rd8.js';
|
|
3
|
+
import { E as EspnHeadshotLeague } from './headshots-B2J_LOO8.js';
|
|
4
|
+
import { W as Which, S as SeasonInput, I as IdSystem, L as League, V as Variant, H as HeadshotIdSystem } from './types-C7n6gJF3.js';
|
|
5
|
+
import './resolve-CQW-jzvr.js';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* The TanStack Charts 1.x adapter: team colours as a chart `color` option (`teamColorScale`) or a list
|
|
9
|
+
* (`teamColorPalette`, also a `theme.palette`), and logos / wordmarks / headshots as TanStack `dot` marks filled with
|
|
10
|
+
* an SVG `<pattern>` (`logoDots`, `wordmarkDots`, `headshotDots`), whose patterns `withPatterns` writes into the
|
|
11
|
+
* chart's own SVG. TanStack Charts has no image mark; a dot keeps its own focus, tooltip, keyboard and keyed update,
|
|
12
|
+
* and the patterns travel with the SVG (server render, `serializeChartSvg`). Pure: `@tanstack/charts` is never
|
|
13
|
+
* imported; the caller hands in its `dot` and `renderChartSvg` (the types are structural, checked against
|
|
14
|
+
* TanStack's own). The SVG renderer only: the Canvas renderer paints an unknown `url(#...)` fill as no fill, so the
|
|
15
|
+
* logos are not drawn there. Call after `loadLeague(league)` / `preloadAll()`.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
/** `teamColorPalette` / `teamColorScale` options. */
|
|
19
|
+
interface TanstackColorOptions {
|
|
20
|
+
/** "primary" (default) or "secondary" colour. */
|
|
21
|
+
which?: Which;
|
|
22
|
+
/** The season the teams are resolved for (a number or string). */
|
|
23
|
+
season?: SeasonInput;
|
|
24
|
+
/** How the team values are read. Default "auto". */
|
|
25
|
+
idSystem?: IdSystem;
|
|
26
|
+
/** The colour of an unresolved team. Default "#808080". */
|
|
27
|
+
fallback?: string;
|
|
28
|
+
/**
|
|
29
|
+
* The chart's background. Given, a team colour under 3:1 contrast against it is swapped for the team's other
|
|
30
|
+
* colour, else moved towards white (on a dark background) or black (a light one) until it reads: dark mode. A
|
|
31
|
+
* colour that would then sit too close to an earlier team's (CIE76 ΔE under 20) takes the team's other colour, or
|
|
32
|
+
* the nearest colour to its own that is distinct and still reads; failing that it is kept, with one warning.
|
|
33
|
+
*/
|
|
34
|
+
background?: string;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* One colour per team, in order: a chart `theme.palette`, or anything else that takes colours by position. With
|
|
38
|
+
* `background`, every colour keeps 3:1 contrast against it (dark mode) and aims to stay distinct from the others.
|
|
39
|
+
* `teamColorScale` puts the same colours on the chart's `color` option, keyed by team.
|
|
40
|
+
*
|
|
41
|
+
* @example
|
|
42
|
+
* ```ts
|
|
43
|
+
* import { loadLeague } from "@sportsdataverse/sdvplot";
|
|
44
|
+
* import { teamColorPalette } from "@sportsdataverse/sdvplot/tanstack";
|
|
45
|
+
*
|
|
46
|
+
* await loadLeague("nfl");
|
|
47
|
+
* teamColorPalette("nfl", ["KC", "LAC", "DEN", "LV"], { background: "#111827" });
|
|
48
|
+
* ```
|
|
49
|
+
*/
|
|
50
|
+
declare function teamColorPalette(league: League, teams: readonly unknown[], o?: TanstackColorOptions): string[];
|
|
51
|
+
/** TanStack's `color` option (`ChartColorOptions`) as an ordinal map from each team value to its colour. */
|
|
52
|
+
interface TanstackColorScale<T = unknown> {
|
|
53
|
+
/** The team values exactly as the data holds them (numbers stay numbers), each once and in order. */
|
|
54
|
+
domain: T[];
|
|
55
|
+
/** Their colours, in the same order. */
|
|
56
|
+
range: string[];
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Team colours as TanStack's chart `color` option: `{ domain, range }`, which TanStack maps by position, so the
|
|
60
|
+
* team in a mark's `color` (or `z`) channel draws in its own colour and the legend lists the teams in this order.
|
|
61
|
+
* Spread it and add a `legend` if wanted. With `background` the colours read on it (dark mode). TanStack matches a
|
|
62
|
+
* value by its type as well (12 and "12" are two keys): pass the teams as the data holds them. A value given twice
|
|
63
|
+
* is kept once; 12 and "12" are both kept, with one colour.
|
|
64
|
+
*
|
|
65
|
+
* @example
|
|
66
|
+
* ```ts
|
|
67
|
+
* import { loadLeague } from "@sportsdataverse/sdvplot";
|
|
68
|
+
* import { teamColorScale } from "@sportsdataverse/sdvplot/tanstack";
|
|
69
|
+
*
|
|
70
|
+
* await loadLeague("nfl");
|
|
71
|
+
* // defineChart({ marks: [lineY(rows, { x: "week", y: "margin", z: "team" })], color: <this> })
|
|
72
|
+
* teamColorScale("nfl", ["KC", "LAC", "DEN", "LV"], { background: "#111827" });
|
|
73
|
+
* ```
|
|
74
|
+
*/
|
|
75
|
+
declare function teamColorScale<T>(league: League, teams: readonly T[], o?: TanstackColorOptions): TanstackColorScale<T>;
|
|
76
|
+
interface Shared {
|
|
77
|
+
/** Image height in px, 1 or more. Default 24. */
|
|
78
|
+
size?: number;
|
|
79
|
+
/** Opacity in [0, 1]: the dot's `fillOpacity`. Default 1. */
|
|
80
|
+
alpha?: number;
|
|
81
|
+
/** url → data URI map from `embedSources`, to inline the images (needed for an image export). */
|
|
82
|
+
embed?: ReadonlyMap<string, string>;
|
|
83
|
+
/**
|
|
84
|
+
* The start of each dot mark's id and pattern id. Default "sdv-logo", "sdv-wordmark" or "sdv-headshot"; give
|
|
85
|
+
* another when a chart draws the same images twice (TanStack needs each mark id once). Letters, digits, `_` and
|
|
86
|
+
* `-` only (it becomes an SVG id); anything else throws `InputError`.
|
|
87
|
+
*/
|
|
88
|
+
id?: string;
|
|
89
|
+
}
|
|
90
|
+
/** `logoDots` / `wordmarkDots` options. */
|
|
91
|
+
interface TanstackDotOptions<X extends string = string, Y extends string = string> extends Shared {
|
|
92
|
+
/** Name of the row field holding each mark's x position (the chart's x channel). */
|
|
93
|
+
x: X;
|
|
94
|
+
/** Name of the row field holding each mark's y position. */
|
|
95
|
+
y: Y;
|
|
96
|
+
/** Name of the row field holding the team (abbreviation, name or id; see `idSystem`). */
|
|
97
|
+
team: string;
|
|
98
|
+
/** The league the teams belong to, e.g. "nfl". */
|
|
99
|
+
league: League;
|
|
100
|
+
/** A season (number or string) for every row, or the name of a row field holding each row's season. */
|
|
101
|
+
season?: SeasonInput;
|
|
102
|
+
/** Logo variant, e.g. "dark". Default "default". */
|
|
103
|
+
variant?: Variant;
|
|
104
|
+
/** How the `team` values are read. Default "auto". */
|
|
105
|
+
idSystem?: IdSystem;
|
|
106
|
+
}
|
|
107
|
+
/** `headshotDots` options. */
|
|
108
|
+
interface TanstackHeadshotOptions<X extends string = string, Y extends string = string> extends Shared {
|
|
109
|
+
/** Name of the row field holding each headshot's x position. */
|
|
110
|
+
x: X;
|
|
111
|
+
/** Name of the row field holding each headshot's y position. */
|
|
112
|
+
y: Y;
|
|
113
|
+
/** Name of the row field holding the player id. */
|
|
114
|
+
player: string;
|
|
115
|
+
/** The league the players belong to, e.g. "nfl". */
|
|
116
|
+
league: EspnHeadshotLeague;
|
|
117
|
+
/** ESPN athlete ids, or nflverse gsis ids (needs `loadGsis()` first). Default "espn". */
|
|
118
|
+
idSystem?: HeadshotIdSystem;
|
|
119
|
+
}
|
|
120
|
+
/** What sdvplot adds to each row of a dot. */
|
|
121
|
+
interface TanstackImageFields {
|
|
122
|
+
/** The resolved team id or player id. */
|
|
123
|
+
sdv_id: string;
|
|
124
|
+
/** The image's URL (or data URI). */
|
|
125
|
+
sdv_src: string;
|
|
126
|
+
/** The image's height in px. */
|
|
127
|
+
sdv_height: number;
|
|
128
|
+
/** The row's index in the rows given: the dot's key, stable across updates. */
|
|
129
|
+
sdv_key: string;
|
|
130
|
+
}
|
|
131
|
+
/** One datum of an sdvplot dot: the caller's row (its own fields, typed) and what it draws. */
|
|
132
|
+
type TanstackImageDatum<R extends object = Row> = R & TanstackImageFields;
|
|
133
|
+
/** What sdvplot hands TanStack's `dot(data, options)` (its `DotOptions`, structurally). */
|
|
134
|
+
interface TanstackDotMarkOptions<X extends string = string, Y extends string = string> {
|
|
135
|
+
/** The mark's id, and its pattern's. */
|
|
136
|
+
id: string;
|
|
137
|
+
/** The row fields of the x and y channels, as given. */
|
|
138
|
+
x: X;
|
|
139
|
+
y: Y;
|
|
140
|
+
/** Each dot's key: its row's index (`sdv_key`). */
|
|
141
|
+
key: (d: TanstackImageFields) => string;
|
|
142
|
+
/** The dot's radius in px: the image fits inside it, uncut. */
|
|
143
|
+
r: number;
|
|
144
|
+
/** `url(#<id>)`: the pattern `withPatterns` writes into the chart's SVG. */
|
|
145
|
+
fill: string;
|
|
146
|
+
fillOpacity: number;
|
|
147
|
+
}
|
|
148
|
+
/** The pattern a dot is filled with: one image, as fractions of the dot's square bounding box. */
|
|
149
|
+
interface TanstackPattern {
|
|
150
|
+
href: string;
|
|
151
|
+
x: number;
|
|
152
|
+
y: number;
|
|
153
|
+
width: number;
|
|
154
|
+
height: number;
|
|
155
|
+
}
|
|
156
|
+
/** One image's dot: pass it to TanStack as `dot(d.data, d.options)`. */
|
|
157
|
+
interface TanstackImageDot<R extends object = Row, X extends string = string, Y extends string = string> {
|
|
158
|
+
data: TanstackImageDatum<R>[];
|
|
159
|
+
options: TanstackDotMarkOptions<X, Y>;
|
|
160
|
+
pattern: TanstackPattern;
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* Team logos as TanStack `dot` marks, one per logo, each filled with its logo: every row's logo is `size` px tall,
|
|
164
|
+
* centred on the row's (`x`, `y`) fields in the chart's scales, and uncut (the dot's radius is the logo box's
|
|
165
|
+
* half-diagonal). Map them to marks with TanStack's `dot`, and pass the chart a `withPatterns` serializer so the
|
|
166
|
+
* patterns are in its SVG. A team with no logo draws nothing and warns once. The dots keep TanStack's focus, tooltip
|
|
167
|
+
* and keyboard navigation; each datum is the caller's row (plus `sdv_*` fields).
|
|
168
|
+
*
|
|
169
|
+
* @example
|
|
170
|
+
* ```ts
|
|
171
|
+
* import { loadLeague } from "@sportsdataverse/sdvplot";
|
|
172
|
+
* import { logoDots } from "@sportsdataverse/sdvplot/tanstack";
|
|
173
|
+
*
|
|
174
|
+
* await loadLeague("nfl");
|
|
175
|
+
* const rows = [
|
|
176
|
+
* { team: "KC", pf: 385, pa: 326 },
|
|
177
|
+
* { team: "LAC", pf: 402, pa: 301 },
|
|
178
|
+
* ];
|
|
179
|
+
* const logos = logoDots(rows, { x: "pf", y: "pa", team: "team", league: "nfl", size: 32 });
|
|
180
|
+
* // defineChart({ marks: logos.map((d) => dot(d.data, d.options)), ... }) and
|
|
181
|
+
* // mountChart(el, { definition, renderSvg: withPatterns(renderChartSvg, logos), ... })
|
|
182
|
+
* logos.map((d) => d.options);
|
|
183
|
+
* ```
|
|
184
|
+
*/
|
|
185
|
+
declare function logoDots<R extends object, X extends keyof R & string, Y extends keyof R & string>(rows: readonly R[], o: TanstackDotOptions<X, Y>): TanstackImageDot<R, X, Y>[];
|
|
186
|
+
/**
|
|
187
|
+
* Team wordmarks as TanStack `dot` marks, each wordmark `size` px tall at its own aspect ratio. The dot's radius is
|
|
188
|
+
* the half-diagonal of the wordmark's box, so a wide wordmark gets a large dot. As `logoDots` otherwise.
|
|
189
|
+
*
|
|
190
|
+
* @example
|
|
191
|
+
* ```ts
|
|
192
|
+
* import { loadLeague } from "@sportsdataverse/sdvplot";
|
|
193
|
+
* import { drawnMarks, wordmarkDots } from "@sportsdataverse/sdvplot/tanstack";
|
|
194
|
+
*
|
|
195
|
+
* await loadLeague("nfl");
|
|
196
|
+
* // [team id, x, y, height in px, wordmark URL]
|
|
197
|
+
* drawnMarks(wordmarkDots([{ team: "LV", wins: 4, week: 18 }], { x: "week", y: "wins", team: "team", league: "nfl", size: 20 }));
|
|
198
|
+
* ```
|
|
199
|
+
*/
|
|
200
|
+
declare function wordmarkDots<R extends object, X extends keyof R & string, Y extends keyof R & string>(rows: readonly R[], o: TanstackDotOptions<X, Y>): TanstackImageDot<R, X, Y>[];
|
|
201
|
+
/**
|
|
202
|
+
* ESPN player headshots as TanStack `dot` marks, from the row's `player` field. A gsis id with no headshot draws
|
|
203
|
+
* nothing and warns once; an ESPN id's URL is built without checking the image exists, so an id ESPN has no photo
|
|
204
|
+
* for keeps its dot, unpainted, with no warning. `idSystem: "gsis"` needs `loadGsis()` first. As `logoDots` otherwise.
|
|
205
|
+
*
|
|
206
|
+
* @example
|
|
207
|
+
* ```ts
|
|
208
|
+
* import { headshotDots } from "@sportsdataverse/sdvplot/tanstack";
|
|
209
|
+
*
|
|
210
|
+
* // Patrick Mahomes (ESPN id 3139477) at his 2024 passing yards and touchdowns, 40 px tall
|
|
211
|
+
* const rows = [{ qb: "3139477", yards: 3928, tds: 26 }];
|
|
212
|
+
* headshotDots(rows, { x: "yards", y: "tds", player: "qb", league: "nfl", size: 40 })[0]?.pattern;
|
|
213
|
+
* ```
|
|
214
|
+
*/
|
|
215
|
+
declare function headshotDots<R extends object, X extends keyof R & string, Y extends keyof R & string>(rows: readonly R[], o: TanstackHeadshotOptions<X, Y>): TanstackImageDot<R, X, Y>[];
|
|
216
|
+
/**
|
|
217
|
+
* TanStack's SVG serializer (`renderChartSvg` from `@tanstack/charts/svg`, or your own) with the dots' patterns
|
|
218
|
+
* written into the chart's SVG, in a `<defs>` of their own: pass the result as the chart's `renderSvg` (vanilla
|
|
219
|
+
* `mountChart`, React and Svelte `<Chart>`). Pattern ids are scoped by the chart's `idPrefix`, as TanStack scopes
|
|
220
|
+
* its gradients, and each dot's `url(#...)` follows. Build it once per set of dots (a module constant or `useMemo`).
|
|
221
|
+
*
|
|
222
|
+
* @example
|
|
223
|
+
* ```ts
|
|
224
|
+
* import { loadLeague } from "@sportsdataverse/sdvplot";
|
|
225
|
+
* import { logoDots, withPatterns } from "@sportsdataverse/sdvplot/tanstack";
|
|
226
|
+
*
|
|
227
|
+
* await loadLeague("nfl");
|
|
228
|
+
* const logos = logoDots([{ team: "KC", pf: 385, pa: 326 }], { x: "pf", y: "pa", team: "team", league: "nfl" });
|
|
229
|
+
* // renderChartSvg from "@tanstack/charts/svg"; here a stand-in that returns an empty chart
|
|
230
|
+
* const renderChartSvg = (_scene: unknown, _o: { idPrefix?: string }) => '<svg class="ts-chart"></svg>';
|
|
231
|
+
* withPatterns(renderChartSvg, logos)({}, { idPrefix: "epa" });
|
|
232
|
+
* ```
|
|
233
|
+
*/
|
|
234
|
+
declare function withPatterns<S, O extends {
|
|
235
|
+
idPrefix?: string;
|
|
236
|
+
}>(renderSvg: (scene: S, options: O) => string, ...sets: readonly (readonly TanstackImageDot<object>[])[]): (scene: S, options: O) => string;
|
|
237
|
+
/** Test hook: [id, x, y, height in px, url] per image of every dot in the sets, in order. */
|
|
238
|
+
declare function drawnMarks(...sets: readonly (readonly TanstackImageDot<object>[])[]): DrawnMark[];
|
|
239
|
+
|
|
240
|
+
export { DrawnMark, Row, type TanstackColorOptions, type TanstackColorScale, type TanstackDotMarkOptions, type TanstackDotOptions, type TanstackHeadshotOptions, type TanstackImageDatum, type TanstackImageDot, type TanstackImageFields, type TanstackPattern, drawnMarks, headshotDots, logoDots, teamColorPalette, teamColorScale, withPatterns, wordmarkDots };
|
package/dist/tanstack.js
ADDED
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { colorPalette, markPlacements, imageSource, aspect } from './chunk-UL5M6KDE.js';
|
|
2
|
+
export { embedSources } from './chunk-UL5M6KDE.js';
|
|
3
|
+
import { checkAlpha } from './chunk-7ULOCCFL.js';
|
|
4
|
+
import './chunk-ZAEQXFJM.js';
|
|
5
|
+
import './chunk-J6R2LEJ7.js';
|
|
6
|
+
import { InputError } from './chunk-E7G7VSLV.js';
|
|
7
|
+
|
|
8
|
+
// src/tanstack.ts
|
|
9
|
+
function teamColorPalette(league, teams, o = {}) {
|
|
10
|
+
return colorPalette(league, teams, o);
|
|
11
|
+
}
|
|
12
|
+
function teamColorScale(league, teams, o = {}) {
|
|
13
|
+
const seen = /* @__PURE__ */ new Map();
|
|
14
|
+
for (const t of teams) if (!seen.has(`${typeof t}:${String(t)}`)) seen.set(`${typeof t}:${String(t)}`, t);
|
|
15
|
+
const domain = [...seen.values()];
|
|
16
|
+
return { domain, range: colorPalette(league, domain, o) };
|
|
17
|
+
}
|
|
18
|
+
function checkSize(size) {
|
|
19
|
+
if (!(typeof size === "number" && Number.isFinite(size) && size > 0))
|
|
20
|
+
throw new InputError(`size is an image height in px > 0, got ${String(size)}`);
|
|
21
|
+
if (size < 1)
|
|
22
|
+
throw new InputError(
|
|
23
|
+
`size is a height in px, not a fraction of the plot (echarts' \`height\`): got ${size}, which draws a sub-pixel image`
|
|
24
|
+
);
|
|
25
|
+
return size;
|
|
26
|
+
}
|
|
27
|
+
function hash(s) {
|
|
28
|
+
let h = 2166136261;
|
|
29
|
+
for (let i = 0; i < s.length; i += 1) h = Math.imul(h ^ s.charCodeAt(i), 16777619);
|
|
30
|
+
return (h >>> 0).toString(36);
|
|
31
|
+
}
|
|
32
|
+
var sdvKey = (d) => d.sdv_key;
|
|
33
|
+
function imageDots(rows, kind, o) {
|
|
34
|
+
const h = checkSize(o.size ?? 24);
|
|
35
|
+
const fillOpacity = checkAlpha(o.alpha ?? 1);
|
|
36
|
+
if (o.id !== void 0 && !/^[A-Za-z0-9_-]+$/.test(o.id))
|
|
37
|
+
throw new InputError(`id may hold only letters, digits, "_" and "-", got ${JSON.stringify(o.id)}`);
|
|
38
|
+
const base = o.id ?? `sdv-${kind}`;
|
|
39
|
+
const dots = /* @__PURE__ */ new Map();
|
|
40
|
+
for (const p of markPlacements(rows, kind, o)) {
|
|
41
|
+
const href = imageSource(p, o.embed);
|
|
42
|
+
let d = dots.get(href);
|
|
43
|
+
if (d === void 0) {
|
|
44
|
+
const a = aspect(p);
|
|
45
|
+
const r = h / 2 * Math.hypot(1, a);
|
|
46
|
+
const w = a * h / (2 * r);
|
|
47
|
+
const ht = h / (2 * r);
|
|
48
|
+
const id = `${base}-${hash(href)}`;
|
|
49
|
+
d = {
|
|
50
|
+
data: [],
|
|
51
|
+
options: { id, x: o.x, y: o.y, key: sdvKey, r, fill: `url(#${id})`, fillOpacity },
|
|
52
|
+
pattern: { href, x: (1 - w) / 2, y: (1 - ht) / 2, width: w, height: ht }
|
|
53
|
+
};
|
|
54
|
+
dots.set(href, d);
|
|
55
|
+
}
|
|
56
|
+
d.data.push({
|
|
57
|
+
...rows[p.index],
|
|
58
|
+
sdv_id: p.id,
|
|
59
|
+
sdv_src: href,
|
|
60
|
+
sdv_height: h,
|
|
61
|
+
sdv_key: String(p.index)
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
return [...dots.values()];
|
|
65
|
+
}
|
|
66
|
+
function logoDots(rows, o) {
|
|
67
|
+
return imageDots(rows, "logo", o);
|
|
68
|
+
}
|
|
69
|
+
function wordmarkDots(rows, o) {
|
|
70
|
+
return imageDots(rows, "wordmark", o);
|
|
71
|
+
}
|
|
72
|
+
function headshotDots(rows, o) {
|
|
73
|
+
return imageDots(rows, "headshot", o);
|
|
74
|
+
}
|
|
75
|
+
var escapeAttr = (s) => s.replace(/[&<>"]/g, (c) => `&#${c.charCodeAt(0)};`);
|
|
76
|
+
var n = (v) => String(Math.round(v * 1e4) / 1e4);
|
|
77
|
+
function withPatterns(renderSvg, ...sets) {
|
|
78
|
+
const patterns = /* @__PURE__ */ new Map();
|
|
79
|
+
for (const d of sets.flat()) patterns.set(d.options.id, d.pattern);
|
|
80
|
+
return (scene, options) => {
|
|
81
|
+
const svg = renderSvg(scene, options);
|
|
82
|
+
if (patterns.size === 0) return svg;
|
|
83
|
+
const end = svg.lastIndexOf("</svg>");
|
|
84
|
+
if (end < 0) throw new InputError("withPatterns: the SVG renderer returned no </svg>");
|
|
85
|
+
const prefix = (options.idPrefix ?? "").replace(/[^a-zA-Z0-9_-]/g, "");
|
|
86
|
+
const scoped = (id) => prefix ? `${prefix}-${id}` : id;
|
|
87
|
+
const body = svg.slice(0, end).replace(/url\(#([^)]+)\)/g, (m, id) => patterns.has(id) ? `url(#${scoped(id)})` : m);
|
|
88
|
+
let defs = "";
|
|
89
|
+
for (const [id, p] of patterns)
|
|
90
|
+
defs += `<pattern data-ts-key="sdv-pattern:${escapeAttr(id)}" id="${escapeAttr(scoped(id))}" patternContentUnits="objectBoundingBox" width="1" height="1"><image href="${escapeAttr(p.href)}" x="${n(p.x)}" y="${n(p.y)}" width="${n(p.width)}" height="${n(p.height)}" preserveAspectRatio="xMidYMid meet"/></pattern>`;
|
|
91
|
+
return `${body}<defs data-ts-key="sdv-patterns">${defs}</defs>${svg.slice(end)}`;
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
function drawnMarks(...sets) {
|
|
95
|
+
return sets.flat().flatMap(
|
|
96
|
+
(d) => d.data.map(
|
|
97
|
+
(r) => [
|
|
98
|
+
r.sdv_id,
|
|
99
|
+
r[d.options.x],
|
|
100
|
+
r[d.options.y],
|
|
101
|
+
r.sdv_height,
|
|
102
|
+
r.sdv_src
|
|
103
|
+
]
|
|
104
|
+
)
|
|
105
|
+
);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export { drawnMarks, headshotDots, logoDots, teamColorPalette, teamColorScale, withPatterns, wordmarkDots };
|
|
109
|
+
//# sourceMappingURL=tanstack.js.map
|
|
110
|
+
//# sourceMappingURL=tanstack.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/tanstack.ts"],"names":[],"mappings":"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","file":"tanstack.js","sourcesContent":["/**\n * The TanStack Charts 1.x adapter: team colours as a chart `color` option (`teamColorScale`) or a list\n * (`teamColorPalette`, also a `theme.palette`), and logos / wordmarks / headshots as TanStack `dot` marks filled with\n * an SVG `<pattern>` (`logoDots`, `wordmarkDots`, `headshotDots`), whose patterns `withPatterns` writes into the\n * chart's own SVG. TanStack Charts has no image mark; a dot keeps its own focus, tooltip, keyboard and keyed update,\n * and the patterns travel with the SVG (server render, `serializeChartSvg`). Pure: `@tanstack/charts` is never\n * imported; the caller hands in its `dot` and `renderChartSvg` (the types are structural, checked against\n * TanStack's own). The SVG renderer only: the Canvas renderer paints an unknown `url(#...)` fill as no fill, so the\n * logos are not drawn there. Call after `loadLeague(league)` / `preloadAll()`.\n */\nimport {\n type DrawnMark,\n type Row,\n aspect,\n checkAlpha,\n colorPalette,\n imageSource,\n markPlacements,\n} from \"./_web.js\";\nimport { InputError } from \"./errors.js\";\nimport type { EspnHeadshotLeague } from \"./headshots.js\";\nimport type { HeadshotIdSystem, IdSystem, League, SeasonInput, Variant, Which } from \"./types.js\";\nexport type { DrawnMark, Row } from \"./_web.js\";\nexport { embedSources } from \"./_web.js\"; // public: build `embed` with it, as for the other adapters\n\n/** `teamColorPalette` / `teamColorScale` options. */\nexport interface TanstackColorOptions {\n /** \"primary\" (default) or \"secondary\" colour. */\n which?: Which;\n /** The season the teams are resolved for (a number or string). */\n season?: SeasonInput;\n /** How the team values are read. Default \"auto\". */\n idSystem?: IdSystem;\n /** The colour of an unresolved team. Default \"#808080\". */\n fallback?: string;\n /**\n * The chart's background. Given, a team colour under 3:1 contrast against it is swapped for the team's other\n * colour, else moved towards white (on a dark background) or black (a light one) until it reads: dark mode. A\n * colour that would then sit too close to an earlier team's (CIE76 ΔE under 20) takes the team's other colour, or\n * the nearest colour to its own that is distinct and still reads; failing that it is kept, with one warning.\n */\n background?: string;\n}\n/**\n * One colour per team, in order: a chart `theme.palette`, or anything else that takes colours by position. With\n * `background`, every colour keeps 3:1 contrast against it (dark mode) and aims to stay distinct from the others.\n * `teamColorScale` puts the same colours on the chart's `color` option, keyed by team.\n *\n * @example\n * ```ts\n * import { loadLeague } from \"@sportsdataverse/sdvplot\";\n * import { teamColorPalette } from \"@sportsdataverse/sdvplot/tanstack\";\n *\n * await loadLeague(\"nfl\");\n * teamColorPalette(\"nfl\", [\"KC\", \"LAC\", \"DEN\", \"LV\"], { background: \"#111827\" });\n * ```\n */\nexport function teamColorPalette(\n league: League,\n teams: readonly unknown[],\n o: TanstackColorOptions = {},\n): string[] {\n return colorPalette(league, teams, o);\n}\n\n/** TanStack's `color` option (`ChartColorOptions`) as an ordinal map from each team value to its colour. */\nexport interface TanstackColorScale<T = unknown> {\n /** The team values exactly as the data holds them (numbers stay numbers), each once and in order. */\n domain: T[];\n /** Their colours, in the same order. */\n range: string[];\n}\n/**\n * Team colours as TanStack's chart `color` option: `{ domain, range }`, which TanStack maps by position, so the\n * team in a mark's `color` (or `z`) channel draws in its own colour and the legend lists the teams in this order.\n * Spread it and add a `legend` if wanted. With `background` the colours read on it (dark mode). TanStack matches a\n * value by its type as well (12 and \"12\" are two keys): pass the teams as the data holds them. A value given twice\n * is kept once; 12 and \"12\" are both kept, with one colour.\n *\n * @example\n * ```ts\n * import { loadLeague } from \"@sportsdataverse/sdvplot\";\n * import { teamColorScale } from \"@sportsdataverse/sdvplot/tanstack\";\n *\n * await loadLeague(\"nfl\");\n * // defineChart({ marks: [lineY(rows, { x: \"week\", y: \"margin\", z: \"team\" })], color: <this> })\n * teamColorScale(\"nfl\", [\"KC\", \"LAC\", \"DEN\", \"LV\"], { background: \"#111827\" });\n * ```\n */\nexport function teamColorScale<T>(\n league: League,\n teams: readonly T[],\n o: TanstackColorOptions = {},\n): TanstackColorScale<T> {\n const seen = new Map<string, T>(); // TanStack's own value key: the type and the value\n for (const t of teams) if (!seen.has(`${typeof t}:${String(t)}`)) seen.set(`${typeof t}:${String(t)}`, t);\n const domain = [...seen.values()];\n // the palette dedupes by team, so 12 and \"12\" share a colour\n return { domain, range: colorPalette(league, domain, o) };\n}\n\ninterface Shared {\n /** Image height in px, 1 or more. Default 24. */\n size?: number;\n /** Opacity in [0, 1]: the dot's `fillOpacity`. Default 1. */\n alpha?: number;\n /** url → data URI map from `embedSources`, to inline the images (needed for an image export). */\n embed?: ReadonlyMap<string, string>;\n /**\n * The start of each dot mark's id and pattern id. Default \"sdv-logo\", \"sdv-wordmark\" or \"sdv-headshot\"; give\n * another when a chart draws the same images twice (TanStack needs each mark id once). Letters, digits, `_` and\n * `-` only (it becomes an SVG id); anything else throws `InputError`.\n */\n id?: string;\n}\n/** `logoDots` / `wordmarkDots` options. */\nexport interface TanstackDotOptions<X extends string = string, Y extends string = string> extends Shared {\n /** Name of the row field holding each mark's x position (the chart's x channel). */\n x: X;\n /** Name of the row field holding each mark's y position. */\n y: Y;\n /** Name of the row field holding the team (abbreviation, name or id; see `idSystem`). */\n team: string;\n /** The league the teams belong to, e.g. \"nfl\". */\n league: League;\n /** A season (number or string) for every row, or the name of a row field holding each row's season. */\n season?: SeasonInput;\n /** Logo variant, e.g. \"dark\". Default \"default\". */\n variant?: Variant;\n /** How the `team` values are read. Default \"auto\". */\n idSystem?: IdSystem;\n}\n/** `headshotDots` options. */\nexport interface TanstackHeadshotOptions<X extends string = string, Y extends string = string>\n extends Shared {\n /** Name of the row field holding each headshot's x position. */\n x: X;\n /** Name of the row field holding each headshot's y position. */\n y: Y;\n /** Name of the row field holding the player id. */\n player: string;\n /** The league the players belong to, e.g. \"nfl\". */\n league: EspnHeadshotLeague;\n /** ESPN athlete ids, or nflverse gsis ids (needs `loadGsis()` first). Default \"espn\". */\n idSystem?: HeadshotIdSystem;\n}\n\n/** What sdvplot adds to each row of a dot. */\nexport interface TanstackImageFields {\n /** The resolved team id or player id. */\n sdv_id: string;\n /** The image's URL (or data URI). */\n sdv_src: string;\n /** The image's height in px. */\n sdv_height: number;\n /** The row's index in the rows given: the dot's key, stable across updates. */\n sdv_key: string;\n}\n/** One datum of an sdvplot dot: the caller's row (its own fields, typed) and what it draws. */\nexport type TanstackImageDatum<R extends object = Row> = R & TanstackImageFields;\n/** What sdvplot hands TanStack's `dot(data, options)` (its `DotOptions`, structurally). */\nexport interface TanstackDotMarkOptions<X extends string = string, Y extends string = string> {\n /** The mark's id, and its pattern's. */\n id: string;\n /** The row fields of the x and y channels, as given. */\n x: X;\n y: Y;\n /** Each dot's key: its row's index (`sdv_key`). */\n key: (d: TanstackImageFields) => string;\n /** The dot's radius in px: the image fits inside it, uncut. */\n r: number;\n /** `url(#<id>)`: the pattern `withPatterns` writes into the chart's SVG. */\n fill: string;\n fillOpacity: number;\n}\n/** The pattern a dot is filled with: one image, as fractions of the dot's square bounding box. */\nexport interface TanstackPattern {\n href: string;\n x: number;\n y: number;\n width: number;\n height: number;\n}\n/** One image's dot: pass it to TanStack as `dot(d.data, d.options)`. */\nexport interface TanstackImageDot<\n R extends object = Row,\n X extends string = string,\n Y extends string = string,\n> {\n data: TanstackImageDatum<R>[];\n options: TanstackDotMarkOptions<X, Y>;\n pattern: TanstackPattern;\n}\n\nfunction checkSize(size: unknown): number {\n if (!(typeof size === \"number\" && Number.isFinite(size) && size > 0))\n throw new InputError(`size is an image height in px > 0, got ${String(size)}`);\n if (size < 1)\n throw new InputError(\n `size is a height in px, not a fraction of the plot (echarts' \\`height\\`): got ${size}, which draws a sub-pixel image`,\n );\n return size;\n}\n/** FNV-1a, base 36: a short id that stays the same for the same image. */\nfunction hash(s: string): string {\n let h = 2166136261;\n for (let i = 0; i < s.length; i += 1) h = Math.imul(h ^ s.charCodeAt(i), 16777619);\n return (h >>> 0).toString(36);\n}\n\nconst sdvKey = (d: TanstackImageFields): string => d.sdv_key;\n\nfunction imageDots<R extends object, X extends string, Y extends string>(\n rows: readonly R[],\n kind: \"logo\" | \"wordmark\" | \"headshot\",\n o: TanstackDotOptions<X, Y> | TanstackHeadshotOptions<X, Y>,\n): TanstackImageDot<R, X, Y>[] {\n const h = checkSize(o.size ?? 24);\n const fillOpacity = checkAlpha(o.alpha ?? 1);\n // the id goes into an SVG id and a url(#...) fill, which TanStack escapes: only characters that need no escaping\n if (o.id !== undefined && !/^[A-Za-z0-9_-]+$/.test(o.id))\n throw new InputError(`id may hold only letters, digits, \"_\" and \"-\", got ${JSON.stringify(o.id)}`);\n const base = o.id ?? `sdv-${kind}`;\n // TanStack paints a dot's fill once per mark: one dot mark per image, its rows in their order\n const dots = new Map<string, TanstackImageDot<R, X, Y>>();\n for (const p of markPlacements(rows as readonly Row[], kind, o)) {\n const href = imageSource(p, o.embed);\n let d = dots.get(href);\n if (d === undefined) {\n const a = aspect(p);\n // the image's box (a·h by h) inscribed in the dot's circle, so the circle never cuts it\n const r = (h / 2) * Math.hypot(1, a);\n const w = (a * h) / (2 * r);\n const ht = h / (2 * r);\n const id = `${base}-${hash(href)}`;\n d = {\n data: [],\n options: { id, x: o.x, y: o.y, key: sdvKey, r, fill: `url(#${id})`, fillOpacity },\n pattern: { href, x: (1 - w) / 2, y: (1 - ht) / 2, width: w, height: ht },\n };\n dots.set(href, d);\n }\n // the whole row: a tooltip or a caller's channel callback reads its fields\n d.data.push({\n ...rows[p.index],\n sdv_id: p.id,\n sdv_src: href,\n sdv_height: h,\n sdv_key: String(p.index),\n } as TanstackImageDatum<R>);\n }\n return [...dots.values()];\n}\n\n/**\n * Team logos as TanStack `dot` marks, one per logo, each filled with its logo: every row's logo is `size` px tall,\n * centred on the row's (`x`, `y`) fields in the chart's scales, and uncut (the dot's radius is the logo box's\n * half-diagonal). Map them to marks with TanStack's `dot`, and pass the chart a `withPatterns` serializer so the\n * patterns are in its SVG. A team with no logo draws nothing and warns once. The dots keep TanStack's focus, tooltip\n * and keyboard navigation; each datum is the caller's row (plus `sdv_*` fields).\n *\n * @example\n * ```ts\n * import { loadLeague } from \"@sportsdataverse/sdvplot\";\n * import { logoDots } from \"@sportsdataverse/sdvplot/tanstack\";\n *\n * await loadLeague(\"nfl\");\n * const rows = [\n * { team: \"KC\", pf: 385, pa: 326 },\n * { team: \"LAC\", pf: 402, pa: 301 },\n * ];\n * const logos = logoDots(rows, { x: \"pf\", y: \"pa\", team: \"team\", league: \"nfl\", size: 32 });\n * // defineChart({ marks: logos.map((d) => dot(d.data, d.options)), ... }) and\n * // mountChart(el, { definition, renderSvg: withPatterns(renderChartSvg, logos), ... })\n * logos.map((d) => d.options);\n * ```\n */\nexport function logoDots<R extends object, X extends keyof R & string, Y extends keyof R & string>(\n rows: readonly R[],\n o: TanstackDotOptions<X, Y>,\n): TanstackImageDot<R, X, Y>[] {\n return imageDots(rows, \"logo\", o);\n}\n/**\n * Team wordmarks as TanStack `dot` marks, each wordmark `size` px tall at its own aspect ratio. The dot's radius is\n * the half-diagonal of the wordmark's box, so a wide wordmark gets a large dot. As `logoDots` otherwise.\n *\n * @example\n * ```ts\n * import { loadLeague } from \"@sportsdataverse/sdvplot\";\n * import { drawnMarks, wordmarkDots } from \"@sportsdataverse/sdvplot/tanstack\";\n *\n * await loadLeague(\"nfl\");\n * // [team id, x, y, height in px, wordmark URL]\n * drawnMarks(wordmarkDots([{ team: \"LV\", wins: 4, week: 18 }], { x: \"week\", y: \"wins\", team: \"team\", league: \"nfl\", size: 20 }));\n * ```\n */\nexport function wordmarkDots<R extends object, X extends keyof R & string, Y extends keyof R & string>(\n rows: readonly R[],\n o: TanstackDotOptions<X, Y>,\n): TanstackImageDot<R, X, Y>[] {\n return imageDots(rows, \"wordmark\", o);\n}\n/**\n * ESPN player headshots as TanStack `dot` marks, from the row's `player` field. A gsis id with no headshot draws\n * nothing and warns once; an ESPN id's URL is built without checking the image exists, so an id ESPN has no photo\n * for keeps its dot, unpainted, with no warning. `idSystem: \"gsis\"` needs `loadGsis()` first. As `logoDots` otherwise.\n *\n * @example\n * ```ts\n * import { headshotDots } from \"@sportsdataverse/sdvplot/tanstack\";\n *\n * // Patrick Mahomes (ESPN id 3139477) at his 2024 passing yards and touchdowns, 40 px tall\n * const rows = [{ qb: \"3139477\", yards: 3928, tds: 26 }];\n * headshotDots(rows, { x: \"yards\", y: \"tds\", player: \"qb\", league: \"nfl\", size: 40 })[0]?.pattern;\n * ```\n */\nexport function headshotDots<R extends object, X extends keyof R & string, Y extends keyof R & string>(\n rows: readonly R[],\n o: TanstackHeadshotOptions<X, Y>,\n): TanstackImageDot<R, X, Y>[] {\n return imageDots(rows, \"headshot\", o);\n}\n\nconst escapeAttr = (s: string): string => s.replace(/[&<>\"]/g, (c) => `&#${c.charCodeAt(0)};`);\nconst n = (v: number): string => String(Math.round(v * 1e4) / 1e4);\n\n/**\n * TanStack's SVG serializer (`renderChartSvg` from `@tanstack/charts/svg`, or your own) with the dots' patterns\n * written into the chart's SVG, in a `<defs>` of their own: pass the result as the chart's `renderSvg` (vanilla\n * `mountChart`, React and Svelte `<Chart>`). Pattern ids are scoped by the chart's `idPrefix`, as TanStack scopes\n * its gradients, and each dot's `url(#...)` follows. Build it once per set of dots (a module constant or `useMemo`).\n *\n * @example\n * ```ts\n * import { loadLeague } from \"@sportsdataverse/sdvplot\";\n * import { logoDots, withPatterns } from \"@sportsdataverse/sdvplot/tanstack\";\n *\n * await loadLeague(\"nfl\");\n * const logos = logoDots([{ team: \"KC\", pf: 385, pa: 326 }], { x: \"pf\", y: \"pa\", team: \"team\", league: \"nfl\" });\n * // renderChartSvg from \"@tanstack/charts/svg\"; here a stand-in that returns an empty chart\n * const renderChartSvg = (_scene: unknown, _o: { idPrefix?: string }) => '<svg class=\"ts-chart\"></svg>';\n * withPatterns(renderChartSvg, logos)({}, { idPrefix: \"epa\" });\n * ```\n */\nexport function withPatterns<S, O extends { idPrefix?: string }>(\n renderSvg: (scene: S, options: O) => string,\n ...sets: readonly (readonly TanstackImageDot<object>[])[]\n): (scene: S, options: O) => string {\n const patterns = new Map<string, TanstackPattern>();\n for (const d of sets.flat()) patterns.set(d.options.id, d.pattern);\n return (scene, options) => {\n const svg = renderSvg(scene, options);\n if (patterns.size === 0) return svg;\n const end = svg.lastIndexOf(\"</svg>\");\n if (end < 0) throw new InputError(\"withPatterns: the SVG renderer returned no </svg>\");\n // TanStack's own id scoping: the prefix with everything but [A-Za-z0-9_-] removed\n const prefix = (options.idPrefix ?? \"\").replace(/[^a-zA-Z0-9_-]/g, \"\");\n const scoped = (id: string) => (prefix ? `${prefix}-${id}` : id);\n const body = svg\n .slice(0, end)\n .replace(/url\\(#([^)]+)\\)/g, (m, id: string) => (patterns.has(id) ? `url(#${scoped(id)})` : m));\n let defs = \"\";\n for (const [id, p] of patterns)\n defs += `<pattern data-ts-key=\"sdv-pattern:${escapeAttr(id)}\" id=\"${escapeAttr(scoped(id))}\" patternContentUnits=\"objectBoundingBox\" width=\"1\" height=\"1\"><image href=\"${escapeAttr(p.href)}\" x=\"${n(p.x)}\" y=\"${n(p.y)}\" width=\"${n(p.width)}\" height=\"${n(p.height)}\" preserveAspectRatio=\"xMidYMid meet\"/></pattern>`;\n return `${body}<defs data-ts-key=\"sdv-patterns\">${defs}</defs>${svg.slice(end)}`;\n };\n}\n\n/** Test hook: [id, x, y, height in px, url] per image of every dot in the sets, in order. */\nexport function drawnMarks(...sets: readonly (readonly TanstackImageDot<object>[])[]): DrawnMark[] {\n return sets\n .flat()\n .flatMap((d) =>\n d.data.map(\n (r): DrawnMark => [\n r.sdv_id,\n (r as Row)[d.options.x],\n (r as Row)[d.options.y],\n r.sdv_height,\n r.sdv_src,\n ],\n ),\n );\n}\n"]}
|
package/dist/testing.js
CHANGED
|
@@ -1,10 +1,9 @@
|
|
|
1
|
-
import './chunk-
|
|
2
|
-
import './chunk-
|
|
1
|
+
import './chunk-J4P4HDBF.js';
|
|
2
|
+
import './chunk-DYMUVE6K.js';
|
|
3
3
|
import './chunk-4C3GX6QO.js';
|
|
4
|
-
import './chunk-
|
|
5
|
-
import {
|
|
4
|
+
import { logoUrlSync } from './chunk-7ULOCCFL.js';
|
|
5
|
+
import { resolveSync, ESPN_HEADSHOT_LEAGUES, headshotUrl } from './chunk-ZAEQXFJM.js';
|
|
6
6
|
import './chunk-J6R2LEJ7.js';
|
|
7
|
-
import { resolveSync, ESPN_HEADSHOT_LEAGUES, headshotUrl } from './chunk-TAXO3VXH.js';
|
|
8
7
|
import { setWarningHandler, UnsupportedTargetError, resetWarnings, InputError } from './chunk-E7G7VSLV.js';
|
|
9
8
|
|
|
10
9
|
// src/testing/index.ts
|
package/dist/testing.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/testing/index.ts"],"names":["mk"],"mappings":"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","file":"testing.js","sourcesContent":["import {\n ESPN_HEADSHOT_LEAGUES,\n type EspnHeadshotLeague,\n InputError,\n type League,\n UnsupportedTargetError,\n headshotUrl,\n logoUrlSync,\n resetWarnings,\n resolveSync,\n setWarningHandler,\n} from \"../index.js\";\nimport type { Axis } from \"../plot/axis.js\";\nimport type { Value } from \"../resolve.js\";\n\nexport interface DrawnMark {\n id: string;\n x: Value;\n y: Value;\n /** Drawn image height as a fraction of the frame height the render saw. */\n height: number;\n url: string;\n kind: string;\n}\nconst num = (s: string | null): Value =>\n s === null || s === \"\" ? null : Number.isNaN(Number(s)) ? s : Number(s);\n\n/** Every `<image data-sdv-id>` under `node`, in document order. */\nexport function drawnMarks(node: ParentNode): DrawnMark[] {\n return Array.from(node.querySelectorAll(\"image[data-sdv-id]\")).map((img) => ({\n id: img.getAttribute(\"data-sdv-id\") ?? \"\",\n x: num(img.getAttribute(\"data-sdv-x\")),\n y: num(img.getAttribute(\"data-sdv-y\")),\n height: Number(img.getAttribute(\"height\")) / Number(img.getAttribute(\"data-sdv-frame\")),\n url: img.getAttribute(\"href\") ?? \"\",\n kind: img.getAttribute(\"data-sdv-kind\") ?? \"\",\n }));\n}\n\nexport interface ContractMarkOptions {\n league: League;\n height?: number;\n alpha?: number;\n}\n/** Adapters must accept typed arrays (e.g. `Float64Array`) for the x and y positions: rule 3 passes them. */\nexport interface ContractAdapter<T> {\n name: string;\n addLogos(\n target: T,\n x: ArrayLike<Value>,\n y: ArrayLike<Value>,\n teams: readonly Value[],\n o: ContractMarkOptions,\n ): T | Promise<T>;\n addWordmarks(\n target: T,\n x: ArrayLike<Value>,\n y: ArrayLike<Value>,\n teams: readonly Value[],\n o: ContractMarkOptions,\n ): T | Promise<T>;\n /** `league` is narrowed to the ESPN-headshot leagues: the contract checks that before calling, so adapters need no cast. */\n addHeadshots(\n target: T,\n x: ArrayLike<Value>,\n y: ArrayLike<Value>,\n players: readonly Value[],\n o: Omit<ContractMarkOptions, \"league\"> & { league: EspnHeadshotLeague },\n ): T | Promise<T>;\n /** Throws `UnsupportedTargetError` when `supportsAxisLogos` is false. */\n axisLogos(target: T, axis: \"x\" | \"y\", o: { league: League; height?: number }): T | Promise<T>;\n supportsAxisLogos: boolean;\n drawnMarks(target: T): readonly DrawnMark[];\n drawnAxisMarks(target: T, axis: \"x\" | \"y\"): readonly { id: string; tick: number; height: number }[];\n visibleAxisLabels(target: T, axis: \"x\" | \"y\"): readonly string[];\n}\n\n/** `message` starts with \"rule N (...): ...\" so a test can match `/^rule N/`. */\nexport class ContractError extends Error {\n constructor(\n readonly rule: string,\n msg: string,\n ) {\n super(`${rule}: ${msg}`);\n this.name = \"ContractError\";\n }\n}\nconst fail = (rule: string, msg: string): never => {\n throw new ContractError(rule, msg);\n};\nconst xyz = (m: readonly DrawnMark[]): unknown[][] => m.map((d) => [d.id, d.x, d.y]);\nconst same = (a: unknown, b: unknown): boolean => JSON.stringify(a) === JSON.stringify(b);\n\n// There is no getter for the current warning handler, so a previous one cannot be restored:\n// this leaves the default handler (null) installed.\nasync function counted<R>(fn: () => R | Promise<R>): Promise<[R, number]> {\n let n = 0;\n resetWarnings();\n setWarningHandler(() => {\n n++;\n });\n try {\n return [await fn(), n];\n } finally {\n setWarningHandler(null);\n }\n}\n/** Plain data: an array, or an object whose prototype is Object.prototype or null (not a class instance or DOM node). */\nconst plain = (v: unknown): v is object =>\n Array.isArray(v) ||\n (typeof v === \"object\" && v !== null && [Object.prototype, null].includes(Object.getPrototypeOf(v)));\n/** Freezes the plain-data part of `v` (plain objects and arrays, recursively); class instances are left alone. */\nfunction deepFreeze<V>(v: V): V {\n if (plain(v) && !Object.isFrozen(v)) {\n Object.freeze(v);\n for (const x of Object.values(v)) deepFreeze(x);\n }\n return v;\n}\n/** The plain-data part of `v` as JSON, class instances as \"[object]\". */\nconst snapshot = (v: unknown): string =>\n JSON.stringify(v, (_k, x: unknown) => (typeof x === \"object\" && x !== null && !plain(x) ? \"[object]\" : x));\n\n/** null when `fn` threw InputError; otherwise what went wrong, for the failure message. */\nasync function notInputError(fn: () => unknown): Promise<string | null> {\n try {\n await fn();\n } catch (e) {\n if (e instanceof InputError) return null;\n return `threw ${e instanceof Error ? `${e.name}: ${e.message}` : String(e)}`;\n }\n return \"did not throw\";\n}\n\nexport interface ContractOptions<T> {\n makeTarget: () => T;\n makeAxisTarget?: (categories: readonly string[]) => T;\n league?: League;\n known?: readonly [string, string];\n knownWordmarks?: readonly [string, string];\n players?: readonly [string, string];\n}\n\n/**\n * Port of Python `sdvplot.testing.check_adapter_contract` (rules 0-8), plus rule 9: no verb mutates its input (the\n * target's plain-data part and the position and value arrays, deep-frozen). Rejects with a `ContractError`\n * naming the first rule broken. Warnings are once per call (one message listing every unresolved value).\n * Resets the warning handler to null (the default) when done: the previous handler cannot be restored.\n */\nexport async function checkAdapterContract<T>(a: ContractAdapter<T>, o: ContractOptions<T>): Promise<void> {\n const league = o.league ?? \"nfl\";\n const [ka, kb] = o.known ?? [\"LV\", \"LAR\"];\n const [wa, wb] = o.knownWordmarks ?? [\"LV\", \"LAC\"];\n const [p, q] = o.players ?? [\"3139477\", \"4241479\"];\n const xs = [10, 20];\n const ys = [-3, -7];\n // rule 0: shape (JS has no dispatch front door)\n for (const k of [\n \"addLogos\",\n \"addWordmarks\",\n \"addHeadshots\",\n \"axisLogos\",\n \"drawnMarks\",\n \"drawnAxisMarks\",\n \"visibleAxisLabels\",\n ] as const)\n if (typeof a[k] !== \"function\") fail(\"rule 0 (shape)\", `${a.name} lacks ${k}`);\n if (typeof a.supportsAxisLogos !== \"boolean\") fail(\"rule 0 (shape)\", \"supportsAxisLogos must be boolean\");\n\n type Verb = (\n t: T,\n x: ArrayLike<Value>,\n y: ArrayLike<Value>,\n v: readonly Value[],\n opts: { height?: number; alpha?: number },\n ) => T | Promise<T>;\n // rules 1, 2, 4 for one image verb; wordmarks (5) and headshots (6) reuse it with their own rule labels\n const checkVerb = async (\n verb: Verb,\n vals: readonly [string, string],\n ids: readonly [string, string],\n urls: readonly [string | undefined, string | undefined],\n r: readonly [string, string, string],\n ): Promise<void> => {\n const want = [\n [ids[0], 10, -3],\n [ids[1], 20, -7],\n ];\n const [t1, n1] = await counted(() => verb(o.makeTarget(), xs, ys, vals, {}));\n const m1 = a.drawnMarks(t1);\n if (!same(xyz(m1), want)) fail(r[0], `expected ${JSON.stringify(want)}, drew ${JSON.stringify(xyz(m1))}`);\n if (n1 !== 0) fail(r[0], `known values warned ${n1} time(s)`);\n m1.forEach((d, i) => {\n const u = urls[i];\n if (u && d.url !== u) fail(r[0], `${d.id} drew ${d.url}, its mark is ${u}`);\n });\n const [t2, n2] = await counted(() => verb(o.makeTarget(), xs, ys, [\"XXX\", vals[1]], {}));\n if (!same(xyz(a.drawnMarks(t2)), [want[1]]))\n fail(r[1], \"an unknown value must be skipped with its own x/y\");\n if (n2 !== 1) fail(r[1], `one unknown value must warn exactly once, warned ${n2}`);\n const [, n3] = await counted(() =>\n verb(o.makeTarget(), [1, 2, 3], [1, 2, 3], [\"XXX\", \"YYY\", vals[0]], {}),\n );\n if (n3 !== 1)\n fail(\n r[1],\n `two distinct unknown values must warn exactly once per call (one message listing both), warned ${n3}`,\n );\n for (const h of [0.1, 0.25, 1]) {\n const t = await verb(o.makeTarget(), xs, ys, vals, { height: h });\n for (const d of a.drawnMarks(t))\n if (Math.abs(d.height - h) / h > 0.01) fail(r[2], `asked height ${h}, drew ${d.height}`);\n }\n for (const bad of [0, 1.5, 40]) {\n const why = await notInputError(() => verb(o.makeTarget(), xs, ys, vals, { height: bad }));\n if (why) fail(r[2], `height ${bad} must throw InputError when the verb is called, ${why}`);\n }\n };\n const [ia = \"\", ib = \"\"] = resolveSync([ka, kb], league);\n await checkVerb(\n (t, x, y, v, opts) => a.addLogos(t, x, y, v, { league, ...opts }),\n [ka, kb],\n [ia, ib],\n [logoUrlSync(ka, league), logoUrlSync(kb, league)],\n [\"rule 1 (resolution)\", \"rule 2 (unknown team: warn and skip)\", \"rule 4 (height semantics)\"],\n );\n // rule 3: columnar parity (real Float64Array positions draw what plain arrays draw)\n const tA = await a.addLogos(o.makeTarget(), xs, ys, [ka, kb], { league });\n const tB = await a.addLogos(o.makeTarget(), new Float64Array(xs), new Float64Array(ys), [ka, kb], {\n league,\n });\n if (!same(xyz(a.drawnMarks(tA)), xyz(a.drawnMarks(tB))))\n fail(\"rule 3 (columnar parity)\", \"typed-array positions drew different marks\");\n // rule 5: wordmarks\n const [wia = \"\", wib = \"\"] = resolveSync([wa, wb], league);\n const wm = (t: string): string | undefined => logoUrlSync(t, league, { markType: \"wordmark\" });\n await checkVerb(\n (t, x, y, v, opts) => a.addWordmarks(t, x, y, v, { league, ...opts }),\n [wa, wb],\n [wia, wib],\n [wm(wa), wm(wb)],\n [\"rule 5 (wordmarks: resolution)\", \"rule 5 (wordmarks: warn and skip)\", \"rule 5 (wordmarks: height)\"],\n );\n // rule 6: headshots (ESPN leagues only; the narrowing keeps adapters cast-free)\n if (!Object.hasOwn(ESPN_HEADSHOT_LEAGUES, league))\n fail(\n \"rule 6 (headshots)\",\n `league ${JSON.stringify(league)} has no ESPN headshots; pass an ESPN headshot league`,\n );\n const hl = league as EspnHeadshotLeague;\n await checkVerb(\n (t, x, y, v, opts) => a.addHeadshots(t, x, y, v, { league: hl, ...opts }),\n [p, q],\n [p, q],\n [headshotUrl(p, hl), headshotUrl(q, hl)],\n [\"rule 6 (headshots)\", \"rule 6 (headshots: unknown id)\", \"rule 6 (headshots: height)\"],\n );\n // rule 7: axis logos\n const r7 = \"rule 7 (axis logos)\";\n if (a.supportsAxisLogos) {\n const mk = o.makeAxisTarget;\n if (!mk) return fail(r7, \"makeAxisTarget is required for an adapter that supports axis logos\");\n // an adapter may resolve at construction or only when the target renders: count across both\n const [[marks, shown], n] = await counted(async () => {\n const t = await a.axisLogos(mk([ka, \"XXX\", kb]), \"x\", { league });\n return [a.drawnAxisMarks(t, \"x\"), a.visibleAxisLabels(t, \"x\")] as const;\n });\n if (\n !same(\n marks.map((m) => m.id),\n [ia, ib],\n )\n )\n fail(r7, `expected axis images for [${ia}, ${ib}] in tick order, drew ${JSON.stringify(marks)}`);\n if (!((marks[0]?.tick ?? 0) < (marks[1]?.tick ?? 0)))\n fail(r7, \"axis images must sit at increasing tick positions\");\n if (n !== 1) fail(r7, `an unknown axis category must warn exactly once, warned ${n}`);\n if (!shown.includes(\"XXX\") || shown.includes(ka) || shown.includes(kb))\n fail(r7, `only the unknown category may stay as text, visible labels are ${JSON.stringify(shown)}`);\n for (const h of [0.1, 0.25])\n for (const m of a.drawnAxisMarks(await a.axisLogos(mk([ka, kb]), \"x\", { league, height: h }), \"x\"))\n if (Math.abs(m.height - h) / h > 0.01) fail(`${r7} (height)`, `asked ${h}, drew ${m.height}`);\n } else {\n let threw = false;\n try {\n await a.axisLogos(o.makeTarget(), \"x\", { league });\n } catch (e) {\n threw = e instanceof UnsupportedTargetError;\n }\n if (!threw)\n fail(r7, \"an adapter without axis-logo support must throw UnsupportedTargetError from axisLogos\");\n }\n // rule 8: alpha outside [0, 1] throws on every verb\n const alphaVerbs: [string, (alpha: number) => unknown][] = [\n [\"addLogos\", (alpha) => a.addLogos(o.makeTarget(), [10], [-3], [ka], { league, alpha })],\n [\"addWordmarks\", (alpha) => a.addWordmarks(o.makeTarget(), [10], [-3], [wa], { league, alpha })],\n [\"addHeadshots\", (alpha) => a.addHeadshots(o.makeTarget(), [10], [-3], [p], { league: hl, alpha })],\n ];\n for (const [name, call] of alphaVerbs)\n for (const bad of [-0.1, 1.1]) {\n const why = await notInputError(() => call(bad));\n if (why) fail(\"rule 8 (alpha)\", `${name} with alpha ${bad} must throw InputError, ${why}`);\n }\n // rule 9: no input mutation. A write into a frozen input throws (strict mode); the snapshot catches the rest.\n const frozen = <V>(v: V): V => deepFreeze(structuredClone(v));\n const inputVerbs: [string, () => T, (t: T) => unknown][] = [\n [\"addLogos\", o.makeTarget, (t) => a.addLogos(t, frozen(xs), frozen(ys), frozen([ka, kb]), { league })],\n [\n \"addWordmarks\",\n o.makeTarget,\n (t) => a.addWordmarks(t, frozen(xs), frozen(ys), frozen([wa, wb]), { league }),\n ],\n [\n \"addHeadshots\",\n o.makeTarget,\n (t) => a.addHeadshots(t, frozen(xs), frozen(ys), frozen([p, q]), { league: hl }),\n ],\n ];\n const mk = o.makeAxisTarget;\n if (a.supportsAxisLogos && mk)\n inputVerbs.push([\"axisLogos\", () => mk([ka, kb]), (t) => a.axisLogos(t, \"x\", { league })]);\n for (const [name, make, call] of inputVerbs) {\n const t = deepFreeze(make());\n const before = snapshot(t);\n try {\n await call(t);\n } catch (e) {\n fail(\n \"rule 9 (no input mutation)\",\n `${name} threw on a frozen input: ${e instanceof Error ? e.message : String(e)}`,\n );\n }\n if (snapshot(t) !== before) fail(\"rule 9 (no input mutation)\", `${name} changed its input target`);\n }\n setWarningHandler(null);\n}\n\n/** Axis images (`<image data-sdv-axis>`), in tick-position order; `height` is a fraction of the frame. */\nexport function drawnAxisMarks(node: ParentNode, axis: Axis): { id: string; tick: number; height: number }[] {\n return Array.from(node.querySelectorAll(`image[data-sdv-axis=\"${axis}\"]`))\n .map((img) => ({\n id: img.getAttribute(\"data-sdv-id\") ?? \"\",\n tick: Number(img.getAttribute(\"data-sdv-tick\")),\n height: Number(img.getAttribute(\"height\")) / Number(img.getAttribute(\"data-sdv-frame\")),\n }))\n .sort((a, b) => a.tick - b.tick);\n}\n/** Non-empty tick `<text>` still on the axis (categories that did not resolve to an image). */\nexport function visibleAxisLabels(node: ParentNode, axis: Axis): string[] {\n return Array.from(node.querySelectorAll(`[aria-label=\"${axis}-axis tick label\"] text`))\n .map((t) => t.textContent ?? \"\")\n .filter((s) => s !== \"\");\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/testing/index.ts"],"names":["mk"],"mappings":"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","file":"testing.js","sourcesContent":["import {\n ESPN_HEADSHOT_LEAGUES,\n type EspnHeadshotLeague,\n InputError,\n type League,\n UnsupportedTargetError,\n headshotUrl,\n logoUrlSync,\n resetWarnings,\n resolveSync,\n setWarningHandler,\n} from \"../index.js\";\nimport type { Axis } from \"../plot/axis.js\";\nimport type { Value } from \"../resolve.js\";\n\nexport interface DrawnMark {\n id: string;\n x: Value;\n y: Value;\n /** Drawn image height as a fraction of the frame height the render saw. */\n height: number;\n url: string;\n kind: string;\n}\nconst num = (s: string | null): Value =>\n s === null || s === \"\" ? null : Number.isNaN(Number(s)) ? s : Number(s);\n\n/** Every `<image data-sdv-id>` under `node`, in document order. */\nexport function drawnMarks(node: ParentNode): DrawnMark[] {\n return Array.from(node.querySelectorAll(\"image[data-sdv-id]\")).map((img) => ({\n id: img.getAttribute(\"data-sdv-id\") ?? \"\",\n x: num(img.getAttribute(\"data-sdv-x\")),\n y: num(img.getAttribute(\"data-sdv-y\")),\n height: Number(img.getAttribute(\"height\")) / Number(img.getAttribute(\"data-sdv-frame\")),\n url: img.getAttribute(\"href\") ?? \"\",\n kind: img.getAttribute(\"data-sdv-kind\") ?? \"\",\n }));\n}\n\nexport interface ContractMarkOptions {\n league: League;\n height?: number;\n alpha?: number;\n}\n/** Adapters must accept typed arrays (e.g. `Float64Array`) for the x and y positions: rule 3 passes them. */\nexport interface ContractAdapter<T> {\n name: string;\n addLogos(\n target: T,\n x: ArrayLike<Value>,\n y: ArrayLike<Value>,\n teams: readonly Value[],\n o: ContractMarkOptions,\n ): T | Promise<T>;\n addWordmarks(\n target: T,\n x: ArrayLike<Value>,\n y: ArrayLike<Value>,\n teams: readonly Value[],\n o: ContractMarkOptions,\n ): T | Promise<T>;\n /** `league` is narrowed to the ESPN-headshot leagues: the contract checks that before calling, so adapters need no cast. */\n addHeadshots(\n target: T,\n x: ArrayLike<Value>,\n y: ArrayLike<Value>,\n players: readonly Value[],\n o: Omit<ContractMarkOptions, \"league\"> & { league: EspnHeadshotLeague },\n ): T | Promise<T>;\n /** Throws `UnsupportedTargetError` when `supportsAxisLogos` is false. */\n axisLogos(target: T, axis: \"x\" | \"y\", o: { league: League; height?: number }): T | Promise<T>;\n supportsAxisLogos: boolean;\n drawnMarks(target: T): readonly DrawnMark[];\n drawnAxisMarks(target: T, axis: \"x\" | \"y\"): readonly { id: string; tick: number; height: number }[];\n visibleAxisLabels(target: T, axis: \"x\" | \"y\"): readonly string[];\n}\n\n/** `message` starts with \"rule N (...): ...\" so a test can match `/^rule N/`. */\nexport class ContractError extends Error {\n constructor(\n readonly rule: string,\n msg: string,\n ) {\n super(`${rule}: ${msg}`);\n this.name = \"ContractError\";\n }\n}\nconst fail = (rule: string, msg: string): never => {\n throw new ContractError(rule, msg);\n};\nconst xyz = (m: readonly DrawnMark[]): unknown[][] => m.map((d) => [d.id, d.x, d.y]);\nconst same = (a: unknown, b: unknown): boolean => JSON.stringify(a) === JSON.stringify(b);\n\n// There is no getter for the current warning handler, so a previous one cannot be restored:\n// this leaves the default handler (null) installed.\nasync function counted<R>(fn: () => R | Promise<R>): Promise<[R, number]> {\n let n = 0;\n resetWarnings();\n setWarningHandler(() => {\n n++;\n });\n try {\n return [await fn(), n];\n } finally {\n setWarningHandler(null);\n }\n}\n/** Plain data: an array, or an object whose prototype is Object.prototype or null (not a class instance or DOM node). */\nconst plain = (v: unknown): v is object =>\n Array.isArray(v) ||\n (typeof v === \"object\" && v !== null && [Object.prototype, null].includes(Object.getPrototypeOf(v)));\n/** Freezes the plain-data part of `v` (plain objects and arrays, recursively); class instances are left alone. */\nfunction deepFreeze<V>(v: V): V {\n if (plain(v) && !Object.isFrozen(v)) {\n Object.freeze(v);\n for (const x of Object.values(v)) deepFreeze(x);\n }\n return v;\n}\n/** The plain-data part of `v` as JSON, class instances as \"[object]\". */\nconst snapshot = (v: unknown): string =>\n JSON.stringify(v, (_k, x: unknown) => (typeof x === \"object\" && x !== null && !plain(x) ? \"[object]\" : x));\n\n/** null when `fn` threw InputError; otherwise what went wrong, for the failure message. */\nasync function notInputError(fn: () => unknown): Promise<string | null> {\n try {\n await fn();\n } catch (e) {\n if (e instanceof InputError) return null;\n return `threw ${e instanceof Error ? `${e.name}: ${e.message}` : String(e)}`;\n }\n return \"did not throw\";\n}\n\nexport interface ContractOptions<T> {\n makeTarget: () => T;\n makeAxisTarget?: (categories: readonly string[]) => T;\n league?: League;\n known?: readonly [string, string];\n knownWordmarks?: readonly [string, string];\n players?: readonly [string, string];\n}\n\n/**\n * Port of Python `sdvplot.testing.check_adapter_contract` (rules 0-8), plus rule 9: no verb mutates its input (the\n * target's plain-data part and the position and value arrays, deep-frozen). Rejects with a `ContractError`\n * naming the first rule broken. Warnings are once per call (one message listing every unresolved value).\n * Resets the warning handler to null (the default) when done: the previous handler cannot be restored.\n */\nexport async function checkAdapterContract<T>(a: ContractAdapter<T>, o: ContractOptions<T>): Promise<void> {\n const league = o.league ?? \"nfl\";\n const [ka, kb] = o.known ?? [\"LV\", \"LAR\"];\n const [wa, wb] = o.knownWordmarks ?? [\"LV\", \"LAC\"];\n const [p, q] = o.players ?? [\"3139477\", \"4241479\"];\n const xs = [10, 20];\n const ys = [-3, -7];\n // rule 0: shape (JS has no dispatch front door)\n for (const k of [\n \"addLogos\",\n \"addWordmarks\",\n \"addHeadshots\",\n \"axisLogos\",\n \"drawnMarks\",\n \"drawnAxisMarks\",\n \"visibleAxisLabels\",\n ] as const)\n if (typeof a[k] !== \"function\") fail(\"rule 0 (shape)\", `${a.name} lacks ${k}`);\n if (typeof a.supportsAxisLogos !== \"boolean\") fail(\"rule 0 (shape)\", \"supportsAxisLogos must be boolean\");\n\n type Verb = (\n t: T,\n x: ArrayLike<Value>,\n y: ArrayLike<Value>,\n v: readonly Value[],\n opts: { height?: number; alpha?: number },\n ) => T | Promise<T>;\n // rules 1, 2, 4 for one image verb; wordmarks (5) and headshots (6) reuse it with their own rule labels\n const checkVerb = async (\n verb: Verb,\n vals: readonly [string, string],\n ids: readonly [string, string],\n urls: readonly [string | undefined, string | undefined],\n r: readonly [string, string, string],\n ): Promise<void> => {\n const want = [\n [ids[0], 10, -3],\n [ids[1], 20, -7],\n ];\n const [t1, n1] = await counted(() => verb(o.makeTarget(), xs, ys, vals, {}));\n const m1 = a.drawnMarks(t1);\n if (!same(xyz(m1), want)) fail(r[0], `expected ${JSON.stringify(want)}, drew ${JSON.stringify(xyz(m1))}`);\n if (n1 !== 0) fail(r[0], `known values warned ${n1} time(s)`);\n m1.forEach((d, i) => {\n const u = urls[i];\n if (u && d.url !== u) fail(r[0], `${d.id} drew ${d.url}, its mark is ${u}`);\n });\n const [t2, n2] = await counted(() => verb(o.makeTarget(), xs, ys, [\"XXX\", vals[1]], {}));\n if (!same(xyz(a.drawnMarks(t2)), [want[1]]))\n fail(r[1], \"an unknown value must be skipped with its own x/y\");\n if (n2 !== 1) fail(r[1], `one unknown value must warn exactly once, warned ${n2}`);\n const [, n3] = await counted(() =>\n verb(o.makeTarget(), [1, 2, 3], [1, 2, 3], [\"XXX\", \"YYY\", vals[0]], {}),\n );\n if (n3 !== 1)\n fail(\n r[1],\n `two distinct unknown values must warn exactly once per call (one message listing both), warned ${n3}`,\n );\n for (const h of [0.1, 0.25, 1]) {\n const t = await verb(o.makeTarget(), xs, ys, vals, { height: h });\n for (const d of a.drawnMarks(t))\n if (Math.abs(d.height - h) / h > 0.01) fail(r[2], `asked height ${h}, drew ${d.height}`);\n }\n for (const bad of [0, 1.5, 40]) {\n const why = await notInputError(() => verb(o.makeTarget(), xs, ys, vals, { height: bad }));\n if (why) fail(r[2], `height ${bad} must throw InputError when the verb is called, ${why}`);\n }\n };\n const [ia = \"\", ib = \"\"] = resolveSync([ka, kb], league);\n await checkVerb(\n (t, x, y, v, opts) => a.addLogos(t, x, y, v, { league, ...opts }),\n [ka, kb],\n [ia, ib],\n [logoUrlSync(ka, league), logoUrlSync(kb, league)],\n [\"rule 1 (resolution)\", \"rule 2 (unknown team: warn and skip)\", \"rule 4 (height semantics)\"],\n );\n // rule 3: columnar parity (real Float64Array positions draw what plain arrays draw)\n const tA = await a.addLogos(o.makeTarget(), xs, ys, [ka, kb], { league });\n const tB = await a.addLogos(o.makeTarget(), new Float64Array(xs), new Float64Array(ys), [ka, kb], {\n league,\n });\n if (!same(xyz(a.drawnMarks(tA)), xyz(a.drawnMarks(tB))))\n fail(\"rule 3 (columnar parity)\", \"typed-array positions drew different marks\");\n // rule 5: wordmarks\n const [wia = \"\", wib = \"\"] = resolveSync([wa, wb], league);\n const wm = (t: string): string | undefined => logoUrlSync(t, league, { markType: \"wordmark\" });\n await checkVerb(\n (t, x, y, v, opts) => a.addWordmarks(t, x, y, v, { league, ...opts }),\n [wa, wb],\n [wia, wib],\n [wm(wa), wm(wb)],\n [\"rule 5 (wordmarks: resolution)\", \"rule 5 (wordmarks: warn and skip)\", \"rule 5 (wordmarks: height)\"],\n );\n // rule 6: headshots (ESPN leagues only; the narrowing keeps adapters cast-free)\n if (!Object.hasOwn(ESPN_HEADSHOT_LEAGUES, league))\n fail(\n \"rule 6 (headshots)\",\n `league ${JSON.stringify(league)} has no ESPN headshots; pass an ESPN headshot league`,\n );\n const hl = league as EspnHeadshotLeague;\n await checkVerb(\n (t, x, y, v, opts) => a.addHeadshots(t, x, y, v, { league: hl, ...opts }),\n [p, q],\n [p, q],\n [headshotUrl(p, hl), headshotUrl(q, hl)],\n [\"rule 6 (headshots)\", \"rule 6 (headshots: unknown id)\", \"rule 6 (headshots: height)\"],\n );\n // rule 7: axis logos\n const r7 = \"rule 7 (axis logos)\";\n if (a.supportsAxisLogos) {\n const mk = o.makeAxisTarget;\n if (!mk) return fail(r7, \"makeAxisTarget is required for an adapter that supports axis logos\");\n // an adapter may resolve at construction or only when the target renders: count across both\n const [[marks, shown], n] = await counted(async () => {\n const t = await a.axisLogos(mk([ka, \"XXX\", kb]), \"x\", { league });\n return [a.drawnAxisMarks(t, \"x\"), a.visibleAxisLabels(t, \"x\")] as const;\n });\n if (\n !same(\n marks.map((m) => m.id),\n [ia, ib],\n )\n )\n fail(r7, `expected axis images for [${ia}, ${ib}] in tick order, drew ${JSON.stringify(marks)}`);\n if (!((marks[0]?.tick ?? 0) < (marks[1]?.tick ?? 0)))\n fail(r7, \"axis images must sit at increasing tick positions\");\n if (n !== 1) fail(r7, `an unknown axis category must warn exactly once, warned ${n}`);\n if (!shown.includes(\"XXX\") || shown.includes(ka) || shown.includes(kb))\n fail(r7, `only the unknown category may stay as text, visible labels are ${JSON.stringify(shown)}`);\n for (const h of [0.1, 0.25])\n for (const m of a.drawnAxisMarks(await a.axisLogos(mk([ka, kb]), \"x\", { league, height: h }), \"x\"))\n if (Math.abs(m.height - h) / h > 0.01) fail(`${r7} (height)`, `asked ${h}, drew ${m.height}`);\n } else {\n let threw = false;\n try {\n await a.axisLogos(o.makeTarget(), \"x\", { league });\n } catch (e) {\n threw = e instanceof UnsupportedTargetError;\n }\n if (!threw)\n fail(r7, \"an adapter without axis-logo support must throw UnsupportedTargetError from axisLogos\");\n }\n // rule 8: alpha outside [0, 1] throws on every verb\n const alphaVerbs: [string, (alpha: number) => unknown][] = [\n [\"addLogos\", (alpha) => a.addLogos(o.makeTarget(), [10], [-3], [ka], { league, alpha })],\n [\"addWordmarks\", (alpha) => a.addWordmarks(o.makeTarget(), [10], [-3], [wa], { league, alpha })],\n [\"addHeadshots\", (alpha) => a.addHeadshots(o.makeTarget(), [10], [-3], [p], { league: hl, alpha })],\n ];\n for (const [name, call] of alphaVerbs)\n for (const bad of [-0.1, 1.1]) {\n const why = await notInputError(() => call(bad));\n if (why) fail(\"rule 8 (alpha)\", `${name} with alpha ${bad} must throw InputError, ${why}`);\n }\n // rule 9: no input mutation. A write into a frozen input throws (strict mode); the snapshot catches the rest.\n const frozen = <V>(v: V): V => deepFreeze(structuredClone(v));\n const inputVerbs: [string, () => T, (t: T) => unknown][] = [\n [\"addLogos\", o.makeTarget, (t) => a.addLogos(t, frozen(xs), frozen(ys), frozen([ka, kb]), { league })],\n [\n \"addWordmarks\",\n o.makeTarget,\n (t) => a.addWordmarks(t, frozen(xs), frozen(ys), frozen([wa, wb]), { league }),\n ],\n [\n \"addHeadshots\",\n o.makeTarget,\n (t) => a.addHeadshots(t, frozen(xs), frozen(ys), frozen([p, q]), { league: hl }),\n ],\n ];\n const mk = o.makeAxisTarget;\n if (a.supportsAxisLogos && mk)\n inputVerbs.push([\"axisLogos\", () => mk([ka, kb]), (t) => a.axisLogos(t, \"x\", { league })]);\n for (const [name, make, call] of inputVerbs) {\n const t = deepFreeze(make());\n const before = snapshot(t);\n try {\n await call(t);\n } catch (e) {\n fail(\n \"rule 9 (no input mutation)\",\n `${name} threw on a frozen input: ${e instanceof Error ? e.message : String(e)}`,\n );\n }\n if (snapshot(t) !== before) fail(\"rule 9 (no input mutation)\", `${name} changed its input target`);\n }\n setWarningHandler(null);\n}\n\n/** Axis images (`<image data-sdv-axis>`), in tick-position order; `height` is a fraction of the frame. */\nexport function drawnAxisMarks(node: ParentNode, axis: Axis): { id: string; tick: number; height: number }[] {\n return Array.from(node.querySelectorAll(`image[data-sdv-axis=\"${axis}\"]`))\n .map((img) => ({\n id: img.getAttribute(\"data-sdv-id\") ?? \"\",\n tick: Number(img.getAttribute(\"data-sdv-tick\")),\n height: Number(img.getAttribute(\"height\")) / Number(img.getAttribute(\"data-sdv-frame\")),\n }))\n .sort((a, b) => a.tick - b.tick);\n}\n/** Non-empty tick `<text>` still on the axis (categories that did not resolve to an image). */\nexport function visibleAxisLabels(node: ParentNode, axis: Axis): string[] {\n return Array.from(node.querySelectorAll(`[aria-label=\"${axis}-axis tick label\"] text`))\n .map((t) => t.textContent ?? \"\")\n .filter((s) => s !== \"\");\n}\n"]}
|