@sportsdataverse/sdvplot 0.0.0-stage → 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.
Files changed (245) hide show
  1. package/LICENSE +21 -0
  2. package/NOTICE.md +109 -0
  3. package/README.md +984 -2
  4. package/dist/_web-D7Aa5Rd8.d.ts +69 -0
  5. package/dist/aaf-GJ3SH34T.js +13 -0
  6. package/dist/aaf-HCYN5T6Y.js +37 -0
  7. package/dist/aaf-ZAE5KS2Y.js +13 -0
  8. package/dist/ahl-E66MDVCU.js +66 -0
  9. package/dist/ahl-NPQQWZEV.js +249 -0
  10. package/dist/ahl-QRDU73XX.js +309 -0
  11. package/dist/axis-CraWQRUJ.d.ts +49 -0
  12. package/dist/bins.d.ts +174 -0
  13. package/dist/bins.js +4 -0
  14. package/dist/bins.js.map +1 -0
  15. package/dist/cfb-F42SVS4X.js +5000 -0
  16. package/dist/cfb-P3SHEYRZ.js +692 -0
  17. package/dist/cfb-WXRZANLP.js +10226 -0
  18. package/dist/chartjs-surface.d.ts +42 -0
  19. package/dist/chartjs-surface.js +65 -0
  20. package/dist/chartjs-surface.js.map +1 -0
  21. package/dist/chartjs.d.ts +162 -0
  22. package/dist/chartjs.js +342 -0
  23. package/dist/chartjs.js.map +1 -0
  24. package/dist/chunk-4C3GX6QO.js +92 -0
  25. package/dist/chunk-4C3GX6QO.js.map +1 -0
  26. package/dist/chunk-57WFSI4Q.js +128 -0
  27. package/dist/chunk-57WFSI4Q.js.map +1 -0
  28. package/dist/chunk-7ULOCCFL.js +313 -0
  29. package/dist/chunk-7ULOCCFL.js.map +1 -0
  30. package/dist/chunk-AMXASQHT.js +120 -0
  31. package/dist/chunk-AMXASQHT.js.map +1 -0
  32. package/dist/chunk-B2MP37SR.js +175 -0
  33. package/dist/chunk-B2MP37SR.js.map +1 -0
  34. package/dist/chunk-C2CDY5OG.js +137 -0
  35. package/dist/chunk-C2CDY5OG.js.map +1 -0
  36. package/dist/chunk-DYMUVE6K.js +127 -0
  37. package/dist/chunk-DYMUVE6K.js.map +1 -0
  38. package/dist/chunk-E7G7VSLV.js +43 -0
  39. package/dist/chunk-E7G7VSLV.js.map +1 -0
  40. package/dist/chunk-H2C3FMMA.js +300 -0
  41. package/dist/chunk-H2C3FMMA.js.map +1 -0
  42. package/dist/chunk-J4P4HDBF.js +175 -0
  43. package/dist/chunk-J4P4HDBF.js.map +1 -0
  44. package/dist/chunk-J6R2LEJ7.js +58 -0
  45. package/dist/chunk-J6R2LEJ7.js.map +1 -0
  46. package/dist/chunk-M3XD2OSN.js +132 -0
  47. package/dist/chunk-M3XD2OSN.js.map +1 -0
  48. package/dist/chunk-OIILARXI.js +30 -0
  49. package/dist/chunk-OIILARXI.js.map +1 -0
  50. package/dist/chunk-RPVVJY74.js +170 -0
  51. package/dist/chunk-RPVVJY74.js.map +1 -0
  52. package/dist/chunk-UL5M6KDE.js +205 -0
  53. package/dist/chunk-UL5M6KDE.js.map +1 -0
  54. package/dist/chunk-WQT4IVLX.js +10 -0
  55. package/dist/chunk-WQT4IVLX.js.map +1 -0
  56. package/dist/chunk-ZAEQXFJM.js +556 -0
  57. package/dist/chunk-ZAEQXFJM.js.map +1 -0
  58. package/dist/cricket-KPCYG2HL.js +493 -0
  59. package/dist/cricket-NDXLE25B.js +116 -0
  60. package/dist/cricket-YUUF7P6M.js +160 -0
  61. package/dist/cytoscape.d.ts +188 -0
  62. package/dist/cytoscape.js +219 -0
  63. package/dist/cytoscape.js.map +1 -0
  64. package/dist/d3.d.ts +234 -0
  65. package/dist/d3.js +198 -0
  66. package/dist/d3.js.map +1 -0
  67. package/dist/deckgl.d.ts +232 -0
  68. package/dist/deckgl.js +197 -0
  69. package/dist/deckgl.js.map +1 -0
  70. package/dist/echarts.d.ts +121 -0
  71. package/dist/echarts.js +228 -0
  72. package/dist/echarts.js.map +1 -0
  73. package/dist/echl-3CR4RGFC.js +241 -0
  74. package/dist/echl-NPC32C23.js +107 -0
  75. package/dist/echl-PI33DGJC.js +64 -0
  76. package/dist/export.d.ts +130 -0
  77. package/dist/export.js +258 -0
  78. package/dist/export.js.map +1 -0
  79. package/dist/g2.d.ts +251 -0
  80. package/dist/g2.js +102 -0
  81. package/dist/g2.js.map +1 -0
  82. package/dist/headshots-B2J_LOO8.d.ts +40 -0
  83. package/dist/index.d.ts +183 -0
  84. package/dist/index.js +9 -0
  85. package/dist/index.js.map +1 -0
  86. package/dist/interact.d.ts +567 -0
  87. package/dist/interact.js +809 -0
  88. package/dist/interact.js.map +1 -0
  89. package/dist/lightweight-charts.d.ts +217 -0
  90. package/dist/lightweight-charts.js +155 -0
  91. package/dist/lightweight-charts.js.map +1 -0
  92. package/dist/maplibre.d.ts +271 -0
  93. package/dist/maplibre.js +175 -0
  94. package/dist/maplibre.js.map +1 -0
  95. package/dist/marks-DH-tQIP-.d.ts +75 -0
  96. package/dist/mbb-3TRLVW3H.js +3619 -0
  97. package/dist/mbb-DQUHCHZP.js +5111 -0
  98. package/dist/mbb-PRAIO2P5.js +371 -0
  99. package/dist/milb-6IQ6NCEW.js +291 -0
  100. package/dist/milb-EYY5MD4T.js +1752 -0
  101. package/dist/milb-EYZ4SZLI.js +797 -0
  102. package/dist/mlb-4PBUJSVQ.js +35 -0
  103. package/dist/mlb-FHPY3GQJ.js +701 -0
  104. package/dist/mlb-REVGL3N2.js +665 -0
  105. package/dist/mui.d.ts +338 -0
  106. package/dist/mui.js +184 -0
  107. package/dist/mui.js.map +1 -0
  108. package/dist/nba-ATT775GS.js +485 -0
  109. package/dist/nba-LKM4UHVU.js +482 -0
  110. package/dist/nba-YX7PXM3R.js +35 -0
  111. package/dist/nbagl-3WGZCZ4S.js +37 -0
  112. package/dist/nbagl-LP4XJRAL.js +229 -0
  113. package/dist/nbagl-VTTOU7LN.js +499 -0
  114. package/dist/ncaa_baseball-UV75LNVR.js +2670 -0
  115. package/dist/ncaa_baseball-VD5J5C72.js +419 -0
  116. package/dist/ncaa_baseball-YRBS32D6.js +4592 -0
  117. package/dist/ncaa_mhockey-IMYHS7QS.js +1033 -0
  118. package/dist/ncaa_mhockey-TXTKWHE3.js +115 -0
  119. package/dist/ncaa_mhockey-U3MFW5VT.js +663 -0
  120. package/dist/ncaa_softball-6WR6KS64.js +420 -0
  121. package/dist/ncaa_softball-RZCTIE56.js +2654 -0
  122. package/dist/ncaa_softball-VOD4PVGY.js +4599 -0
  123. package/dist/ncaa_whockey-6XXGUTBK.js +607 -0
  124. package/dist/ncaa_whockey-AKKYKY67.js +53 -0
  125. package/dist/ncaa_whockey-KBEKXFBB.js +293 -0
  126. package/dist/nfl-BDB2HYAR.js +625 -0
  127. package/dist/nfl-EHYHDLJD.js +37 -0
  128. package/dist/nfl-VF4NJCHY.js +514 -0
  129. package/dist/nfl_gsis-UY64JO37.js +4 -0
  130. package/dist/nhl-6WPHME7H.js +511 -0
  131. package/dist/nhl-EMIJFZHY.js +884 -0
  132. package/dist/nhl-ISFUKRFU.js +37 -0
  133. package/dist/nivo.d.ts +193 -0
  134. package/dist/nivo.js +8 -0
  135. package/dist/nivo.js.map +1 -0
  136. package/dist/ohl-2FEM554G.js +113 -0
  137. package/dist/ohl-C5Q4W6JB.js +32 -0
  138. package/dist/ohl-TQ4J46VC.js +66 -0
  139. package/dist/perspective.d.ts +198 -0
  140. package/dist/perspective.js +171 -0
  141. package/dist/perspective.js.map +1 -0
  142. package/dist/phf-H7POU2KW.js +32 -0
  143. package/dist/phf-K5IFMZUM.js +13 -0
  144. package/dist/phf-TWJI3BOW.js +20 -0
  145. package/dist/placement-UWkqUzpa.d.ts +32 -0
  146. package/dist/plot.d.ts +451 -0
  147. package/dist/plot.js +712 -0
  148. package/dist/plot.js.map +1 -0
  149. package/dist/plotly.d.ts +129 -0
  150. package/dist/plotly.js +360 -0
  151. package/dist/plotly.js.map +1 -0
  152. package/dist/pwhl-OX3BO4JI.js +53 -0
  153. package/dist/pwhl-W44OC3C6.js +17 -0
  154. package/dist/pwhl-ZJGG3H7W.js +23 -0
  155. package/dist/qmjhl-5HISEHLL.js +35 -0
  156. package/dist/qmjhl-DA3UHKE5.js +125 -0
  157. package/dist/qmjhl-YG4BOQPF.js +614 -0
  158. package/dist/react.d.ts +377 -0
  159. package/dist/react.js +522 -0
  160. package/dist/react.js.map +1 -0
  161. package/dist/recharts.d.ts +162 -0
  162. package/dist/recharts.js +8 -0
  163. package/dist/recharts.js.map +1 -0
  164. package/dist/resolve-CQW-jzvr.d.ts +28 -0
  165. package/dist/selection-D4T3PkRt.d.ts +140 -0
  166. package/dist/shadcn.d.ts +83 -0
  167. package/dist/shadcn.js +88 -0
  168. package/dist/shadcn.js.map +1 -0
  169. package/dist/shots.d.ts +364 -0
  170. package/dist/shots.js +86 -0
  171. package/dist/shots.js.map +1 -0
  172. package/dist/soccer-7BH3ZLVL.js +7898 -0
  173. package/dist/soccer-MRU25OGE.js +2636 -0
  174. package/dist/soccer-PF4IPPVA.js +12342 -0
  175. package/dist/surface-TR7YFJW3.js +4 -0
  176. package/dist/surface-TR7YFJW3.js.map +1 -0
  177. package/dist/surfaces-MIG7HOFU.js +6 -0
  178. package/dist/surfaces-MIG7HOFU.js.map +1 -0
  179. package/dist/surfaces-W-odWLpT.d.ts +47 -0
  180. package/dist/svelte/HeadshotMark.svelte +25 -0
  181. package/dist/svelte/HeadshotMark.svelte.d.ts +10 -0
  182. package/dist/svelte/HeadshotMark.svelte.d.ts.map +1 -0
  183. package/dist/svelte/LogoMark.svelte +25 -0
  184. package/dist/svelte/LogoMark.svelte.d.ts +10 -0
  185. package/dist/svelte/LogoMark.svelte.d.ts.map +1 -0
  186. package/dist/svelte/Marks.svelte +25 -0
  187. package/dist/svelte/Marks.svelte.d.ts +10 -0
  188. package/dist/svelte/Marks.svelte.d.ts.map +1 -0
  189. package/dist/svelte/Surface.svelte +29 -0
  190. package/dist/svelte/Surface.svelte.d.ts +9 -0
  191. package/dist/svelte/Surface.svelte.d.ts.map +1 -0
  192. package/dist/svelte/SurfaceBackdrop.svelte +26 -0
  193. package/dist/svelte/SurfaceBackdrop.svelte.d.ts +11 -0
  194. package/dist/svelte/SurfaceBackdrop.svelte.d.ts.map +1 -0
  195. package/dist/svelte/index.d.ts +13 -0
  196. package/dist/svelte/index.d.ts.map +1 -0
  197. package/dist/svelte/index.js +11 -0
  198. package/dist/svelte-core.d.ts +139 -0
  199. package/dist/svelte-core.js +123 -0
  200. package/dist/svelte-core.js.map +1 -0
  201. package/dist/tanstack.d.ts +240 -0
  202. package/dist/tanstack.js +110 -0
  203. package/dist/tanstack.js.map +1 -0
  204. package/dist/testing.d.ts +75 -0
  205. package/dist/testing.js +239 -0
  206. package/dist/testing.js.map +1 -0
  207. package/dist/three.d.ts +223 -0
  208. package/dist/three.js +50 -0
  209. package/dist/three.js.map +1 -0
  210. package/dist/tiers-BN4md-GB.d.ts +65 -0
  211. package/dist/types-C7n6gJF3.d.ts +62 -0
  212. package/dist/ufl-7VLZTLTT.js +143 -0
  213. package/dist/ufl-CCMBLPSW.js +100 -0
  214. package/dist/ufl-F2BJRXME.js +16 -0
  215. package/dist/unovis.d.ts +127 -0
  216. package/dist/unovis.js +108 -0
  217. package/dist/unovis.js.map +1 -0
  218. package/dist/uplot.d.ts +120 -0
  219. package/dist/uplot.js +110 -0
  220. package/dist/uplot.js.map +1 -0
  221. package/dist/usfl-4CR5RMZJ.js +32 -0
  222. package/dist/usfl-LD7HP2YK.js +14 -0
  223. package/dist/usfl-VU4YSJQD.js +19 -0
  224. package/dist/ushl-CPG2LB4L.js +73 -0
  225. package/dist/ushl-PQL6HJAD.js +88 -0
  226. package/dist/ushl-ZT2ATPHK.js +22 -0
  227. package/dist/vega.d.ts +85 -0
  228. package/dist/vega.js +362 -0
  229. package/dist/vega.js.map +1 -0
  230. package/dist/victory.d.ts +107 -0
  231. package/dist/victory.js +38 -0
  232. package/dist/victory.js.map +1 -0
  233. package/dist/wbb-3KHCDYTK.js +5087 -0
  234. package/dist/wbb-BMX53Q3Q.js +369 -0
  235. package/dist/wbb-QNRN2CJJ.js +3620 -0
  236. package/dist/whl-4ATYZWUB.js +97 -0
  237. package/dist/whl-RVFPL6MH.js +79 -0
  238. package/dist/whl-Y7ASKYMM.js +28 -0
  239. package/dist/wnba-FWX5SFVI.js +232 -0
  240. package/dist/wnba-GX6HZR4B.js +220 -0
  241. package/dist/wnba-NTMWIPP6.js +20 -0
  242. package/dist/xfl-43R2JMJK.js +30 -0
  243. package/dist/xfl-H3BXQPZZ.js +16 -0
  244. package/dist/xfl-XON7A7FI.js +69 -0
  245. package/package.json +366 -3
@@ -0,0 +1,313 @@
1
+ import { archiveUrl, normSeason, getLeagueSync, resolveSync, loadLeague, ESPN_HEADSHOT_LEAGUES, headshotUrl, normValue, HEADSHOT_ASPECT, loadGsis, VARIANTS } from './chunk-ZAEQXFJM.js';
2
+ import { DownloadError, warn, UnresolvedTeamError, InputError } from './chunk-E7G7VSLV.js';
3
+
4
+ // src/rank.ts
5
+ var SOURCE_RANK = {
6
+ espn: 0,
7
+ nflverse: 1,
8
+ nhl: 1,
9
+ mlbstatic: 1,
10
+ fox: 1,
11
+ hockeytech: 2,
12
+ cricinfo: 2,
13
+ "ncaa.com": 3,
14
+ "nwhl.co": 4,
15
+ shiftstats: 4,
16
+ wayback: 4,
17
+ "aaf.com": 5,
18
+ "aaf-strip-crop": 6
19
+ };
20
+ var desc = (a, b) => a === b ? 0 : a === null ? 1 : b === null ? -1 : a < b ? 1 : -1;
21
+ function compareMarks(a, b) {
22
+ return a.source_rank - b.source_rank || Number(a.valid_to !== null) - Number(b.valid_to !== null) || desc(a.valid_to, b.valid_to) || desc(a.first_seen, b.first_seen) || desc(a.valid_from, b.valid_from) || (a.sha256 < b.sha256 ? -1 : a.sha256 > b.sha256 ? 1 : 0);
23
+ }
24
+
25
+ // src/manifest.ts
26
+ var MANIFEST_URL = "https://sdv.nyc3.cdn.digitaloceanspaces.com/assets/public/manifest/marks.csv";
27
+ var safeArchive = (m) => /^[0-9a-f]{64}$/.test(m.sha256) && /^[a-z0-9]+$/.test(m.ext) && m.archive_url === archiveUrl(m.sha256, m.ext);
28
+ function parseManifestCsv(text) {
29
+ const src = text.replace(/^\uFEFF/, "");
30
+ const records = [];
31
+ let rec = [];
32
+ let cur = "";
33
+ let q = false;
34
+ const endRecord = () => {
35
+ rec.push(cur);
36
+ cur = "";
37
+ if (rec.length > 1 || rec[0] !== "") records.push(rec);
38
+ rec = [];
39
+ };
40
+ for (let i = 0; i < src.length; i++) {
41
+ const c = src[i];
42
+ if (q) {
43
+ if (c !== '"') cur += c;
44
+ else if (src[i + 1] === '"') {
45
+ cur += '"';
46
+ i++;
47
+ } else q = false;
48
+ } else if (c === '"') q = true;
49
+ else if (c === ",") {
50
+ rec.push(cur);
51
+ cur = "";
52
+ } else if (c === "\n") endRecord();
53
+ else if (c === "\r" && src[i + 1] === "\n") continue;
54
+ else cur += c;
55
+ }
56
+ if (cur !== "" || rec.length > 0) endRecord();
57
+ const header = records[0] ?? [];
58
+ return records.slice(1).map((f) => {
59
+ const r = {};
60
+ header.forEach((h, i) => {
61
+ r[h] = f[i] ?? "";
62
+ });
63
+ return r;
64
+ });
65
+ }
66
+ var int = (s) => s === "" ? null : Number.parseInt(s, 10);
67
+ var key = (source, id) => `${source}:${id}`.trim().toLowerCase();
68
+ function markAliases(aliases) {
69
+ const by = /* @__PURE__ */ new Map();
70
+ for (const a of aliases) {
71
+ if (a.id_system !== "mark") continue;
72
+ const k = a.value.trim().toLowerCase();
73
+ const e = by.get(k) ?? {
74
+ teams: /* @__PURE__ */ new Set(),
75
+ from: null,
76
+ to: null,
77
+ anyOpenFrom: false,
78
+ anyOpenTo: false
79
+ };
80
+ e.teams.add(a.team_id);
81
+ if (a.valid_from === null) e.anyOpenFrom = true;
82
+ else e.from = e.from === null ? a.valid_from : Math.min(e.from, a.valid_from);
83
+ if (a.valid_to === null) e.anyOpenTo = true;
84
+ else e.to = e.to === null ? a.valid_to : Math.max(e.to, a.valid_to);
85
+ by.set(k, e);
86
+ }
87
+ const out = /* @__PURE__ */ new Map();
88
+ for (const [k, e] of by)
89
+ if (e.teams.size === 1)
90
+ out.set(k, {
91
+ team_id: [...e.teams][0],
92
+ from: e.anyOpenFrom ? null : e.from,
93
+ to: e.anyOpenTo ? null : e.to
94
+ });
95
+ return out;
96
+ }
97
+ function manifestMarks(league, rows, aliases) {
98
+ const ma = markAliases(aliases);
99
+ const out = [];
100
+ for (const m of rows) {
101
+ if (m.level !== "team" || m.league !== league || !safeArchive(m)) continue;
102
+ const a = ma.get(key(m.source, m.entity_id));
103
+ if (!a) continue;
104
+ const vf = int(m.valid_from);
105
+ const vt = int(m.valid_to);
106
+ out.push({
107
+ team_id: a.team_id,
108
+ mark_type: m.mark_type,
109
+ variant: m.variant,
110
+ valid_from: vf === null ? a.from : a.from === null ? vf : Math.max(vf, a.from),
111
+ // pl.max_horizontal (nulls ignored)
112
+ valid_to: vt === null ? a.to : a.to === null ? vt : Math.min(vt, a.to),
113
+ // pl.min_horizontal
114
+ source: m.source,
115
+ first_seen: m.first_seen,
116
+ sha256: m.sha256,
117
+ ext: m.ext,
118
+ width: int(m.width),
119
+ height: int(m.height),
120
+ source_rank: SOURCE_RANK[m.source] ?? 5,
121
+ // after height: the shard generator's JSON key order
122
+ archive_url: archiveUrl(m.sha256, m.ext)
123
+ });
124
+ }
125
+ return out.sort(compareMarks);
126
+ }
127
+ var cached;
128
+ function resetManifestCache() {
129
+ cached = void 0;
130
+ }
131
+ function fetchManifest(o = {}) {
132
+ const f = o.fetch ?? globalThis.fetch;
133
+ cached ??= (async () => {
134
+ const r = await f(MANIFEST_URL);
135
+ if (!r.ok)
136
+ throw new DownloadError(
137
+ `manifest download failed: ${MANIFEST_URL} answered ${r.status}`,
138
+ MANIFEST_URL,
139
+ r.status
140
+ );
141
+ return parseManifestCsv(await r.text());
142
+ })().catch((e) => {
143
+ cached = void 0;
144
+ throw e;
145
+ });
146
+ return cached;
147
+ }
148
+
149
+ // src/marks.ts
150
+ var ro = (o) => Object.fromEntries(Object.entries(o).filter(([, v]) => v !== void 0));
151
+ var repr = (v) => typeof v === "string" ? `'${v}'` : String(v);
152
+ var covers = (lo, hi, s) => (lo === null || s >= lo) && (hi === null || s <= hi);
153
+ function checkMarkType(markType) {
154
+ if (markType !== "logo" && markType !== "wordmark")
155
+ throw new InputError(`markType must be "logo" or "wordmark", got ${JSON.stringify(markType)}`);
156
+ }
157
+ function checkVariant(variant, league, d) {
158
+ if (variant === "default" || variant === "dark") return;
159
+ if (VARIANTS.includes(variant)) return;
160
+ const known = [...new Set(d.marks.map((m) => m.variant))].sort();
161
+ throw new InputError(
162
+ `unknown variant ${JSON.stringify(variant)} for ${league}; use "default", "dark" or one of ${known.join(", ")}`
163
+ );
164
+ }
165
+ function selectMarkSync(team, league, o = {}) {
166
+ const markType = o.markType ?? "logo";
167
+ const variant = o.variant ?? "default";
168
+ checkMarkType(markType);
169
+ const s = normSeason(o.season, league);
170
+ const d = getLeagueSync(league);
171
+ checkVariant(variant, league, d);
172
+ const teamId = resolveSync(team, league, { season: s });
173
+ if (teamId === void 0) return void 0;
174
+ const rows = d.marks.filter((r) => r.team_id === teamId && r.mark_type === markType);
175
+ const side = variant === "dark" ? "dark" : "light";
176
+ const polarity = (v) => v === `on_${side}` || v.endsWith(`_on_${side}`);
177
+ const requested = (v) => v === variant;
178
+ const dflt = (v) => v === "default";
179
+ const order = side === "dark" ? [requested, polarity, dflt] : [requested, dflt, polarity];
180
+ for (const keep of [...order, null]) {
181
+ const found = keep ? rows.filter((r) => keep(r.variant)) : rows;
182
+ if (s !== null) {
183
+ const covering = found.filter((r) => covers(r.valid_from, r.valid_to, s));
184
+ const dated = covering.filter((r) => r.valid_from !== null || r.valid_to !== null);
185
+ for (const set of [dated, covering]) if (set.length) return set[0];
186
+ }
187
+ if (found.length) return found[0];
188
+ }
189
+ return void 0;
190
+ }
191
+ async function selectMark(team, league, o = {}) {
192
+ await loadLeague(league);
193
+ return selectMarkSync(team, league, o);
194
+ }
195
+ function logoUrlSync(team, league, o = {}) {
196
+ const markType = o.markType ?? "logo";
197
+ checkMarkType(markType);
198
+ checkVariant(o.variant ?? "default", league, getLeagueSync(league));
199
+ const teamId = resolveSync(team, league, ro({ season: o.season, idSystem: o.idSystem, strict: o.strict }));
200
+ if (teamId === void 0) return void 0;
201
+ const row = selectMarkSync(teamId, league, o);
202
+ if (row === void 0) {
203
+ warn(`marks:${league}:${teamId}:${markType}`, `no ${markType} archived for ${repr(team)} (${league})`);
204
+ return void 0;
205
+ }
206
+ return row.archive_url;
207
+ }
208
+ async function logoUrl(team, league, o = {}) {
209
+ await loadLeague(league);
210
+ return logoUrlSync(team, league, o);
211
+ }
212
+ async function marks(team, league, o = {}) {
213
+ const d = await loadLeague(league);
214
+ const teamId = resolveSync(team, league, ro({ season: o.season, idSystem: o.idSystem, strict: true }));
215
+ if (teamId === void 0) throw new UnresolvedTeamError(`marks() needs a team, got ${String(team)}`);
216
+ if (!o.full) return d.marks.filter((r) => r.team_id === teamId);
217
+ const fo = o.fetch === void 0 ? {} : { fetch: o.fetch };
218
+ return manifestMarks(league, await fetchManifest(fo), d.aliases).filter((r) => r.team_id === teamId);
219
+ }
220
+
221
+ // src/placement.ts
222
+ function placedName(p, league, raw) {
223
+ const team = p.mark === null ? void 0 : getLeagueSync(league).teams.find((t) => t.team_id === p.id);
224
+ return team?.abbr || team?.name || String(raw);
225
+ }
226
+ var real = (v) => typeof v === "number" && !Number.isNaN(v);
227
+ function checkHeight(height) {
228
+ if (!real(height) || !(height > 0 && height <= 1))
229
+ throw new InputError(`height is a fraction of the plot height in (0, 1], got ${String(height)}`);
230
+ return height;
231
+ }
232
+ function checkAlpha(alpha) {
233
+ if (!real(alpha) || !(alpha >= 0 && alpha <= 1))
234
+ throw new InputError(`alpha is an opacity in [0, 1], got ${String(alpha)}`);
235
+ return alpha;
236
+ }
237
+ var missing = (v) => v === null || v === void 0 || typeof v === "number" && Number.isNaN(v);
238
+ function skipped(league, reason, values, on) {
239
+ if (!on || values.length === 0) return;
240
+ const extra = values.length > 10 ? ` and ${values.length - 10} more` : "";
241
+ const shown = `${values.slice(0, 10).map(String).join(", ")}${extra}`;
242
+ warn(`place:${league}:${reason}:${shown}`, `skipped ${values.length} point(s) ${reason}: ${shown}`);
243
+ }
244
+ function placeSync(xs, ys, teams, o) {
245
+ const { league, kind = "logo", variant = "default", strict = false, warn: on = true } = o;
246
+ if (!(xs.length === ys.length && ys.length === teams.length))
247
+ throw new InputError(
248
+ `x, y and teams must have the same length, got ${xs.length}, ${ys.length} and ${teams.length}`
249
+ );
250
+ const out = [];
251
+ const missingXY = [];
252
+ if (kind === "headshot") {
253
+ if (!Object.hasOwn(ESPN_HEADSHOT_LEAGUES, league))
254
+ throw new InputError(
255
+ `no ESPN headshots for league ${JSON.stringify(league)}; supported: ${Object.keys(ESPN_HEADSHOT_LEAGUES).sort().join(", ")}`
256
+ );
257
+ const hl = league;
258
+ const gsis = o.idSystem === "gsis";
259
+ const noImage = [];
260
+ teams.forEach((pid, i) => {
261
+ if (missing(pid)) return;
262
+ if (missing(xs[i]) || missing(ys[i])) {
263
+ missingXY.push(pid);
264
+ return;
265
+ }
266
+ const url = headshotUrl(pid, hl, { idSystem: gsis ? "gsis" : "espn" });
267
+ if (url === void 0) {
268
+ noImage.push(pid);
269
+ return;
270
+ }
271
+ const id = gsis ? String(pid).trim() : normValue(pid) ?? String(pid).trim();
272
+ out.push({ index: i, id, x: xs[i], y: ys[i], url, aspect: HEADSHOT_ASPECT, mark: null });
273
+ });
274
+ skipped(league, "with no headshot", noImage, on);
275
+ } else {
276
+ const seasons = Array.isArray(o.season) ? o.season : teams.map(() => o.season);
277
+ const ropts = { season: seasons, strict };
278
+ if (o.idSystem !== void 0) ropts.idSystem = o.idSystem;
279
+ const ids = resolveSync(teams, league, ropts);
280
+ const cache = /* @__PURE__ */ new Map();
281
+ const noMark = [];
282
+ teams.forEach((raw, i) => {
283
+ const id = ids[i];
284
+ if (id === void 0) return;
285
+ if (missing(xs[i]) || missing(ys[i])) {
286
+ missingXY.push(raw);
287
+ return;
288
+ }
289
+ const k = `${id}|${String(seasons[i])}`;
290
+ if (!cache.has(k))
291
+ cache.set(k, selectMarkSync(id, league, { season: seasons[i], variant, markType: kind }));
292
+ const row = cache.get(k);
293
+ if (!row) {
294
+ noMark.push(raw);
295
+ return;
296
+ }
297
+ const aspect = row.width && row.height ? row.width / row.height : null;
298
+ out.push({ index: i, id, x: xs[i], y: ys[i], url: row.archive_url, aspect, mark: row });
299
+ });
300
+ skipped(league, `with no ${kind} archived`, noMark, on);
301
+ }
302
+ skipped(league, "with a missing x or y", missingXY, on);
303
+ return out;
304
+ }
305
+ async function place(xs, ys, teams, o) {
306
+ await loadLeague(o.league);
307
+ if (o.kind === "headshot" && o.idSystem === "gsis") await loadGsis();
308
+ return placeSync(xs, ys, teams, o);
309
+ }
310
+
311
+ export { MANIFEST_URL, checkAlpha, checkHeight, compareMarks, fetchManifest, logoUrl, logoUrlSync, manifestMarks, marks, parseManifestCsv, place, placeSync, placedName, resetManifestCache, selectMark, selectMarkSync };
312
+ //# sourceMappingURL=chunk-7ULOCCFL.js.map
313
+ //# sourceMappingURL=chunk-7ULOCCFL.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/rank.ts","../src/manifest.ts","../src/marks.ts","../src/placement.ts"],"names":[],"mappings":"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","file":"chunk-7ULOCCFL.js","sourcesContent":["/** Source preference, best first; unknown sources rank 5. Shared by the shard generator and the lazy full-manifest path. */\nexport const SOURCE_RANK: Readonly<Record<string, number>> = {\n espn: 0,\n nflverse: 1,\n nhl: 1,\n mlbstatic: 1,\n fox: 1,\n hockeytech: 2,\n cricinfo: 2,\n \"ncaa.com\": 3,\n \"nwhl.co\": 4,\n shiftstats: 4,\n wayback: 4,\n \"aaf.com\": 5,\n \"aaf-strip-crop\": 6,\n};\n\nexport interface Rankable {\n source_rank: number;\n valid_from: number | null;\n valid_to: number | null;\n first_seen: string;\n sha256: string;\n}\nconst desc = <T extends string | number>(a: T | null, b: T | null): number =>\n a === b ? 0 : a === null ? 1 : b === null ? -1 : a < b ? 1 : -1;\n/** Python `_ranked`: source_rank asc, open-ended (valid_to null) first, valid_to desc, first_seen desc, valid_from desc, sha256 asc; nulls last. */\nexport function compareMarks(a: Rankable, b: Rankable): number {\n return (\n a.source_rank - b.source_rank ||\n Number(a.valid_to !== null) - Number(b.valid_to !== null) ||\n desc(a.valid_to, b.valid_to) ||\n desc(a.first_seen, b.first_seen) ||\n desc(a.valid_from, b.valid_from) ||\n (a.sha256 < b.sha256 ? -1 : a.sha256 > b.sha256 ? 1 : 0)\n );\n}\n","import { type Alias, type MarkRow, archiveUrl } from \"./data/index.js\";\nimport { DownloadError } from \"./errors.js\";\nimport { SOURCE_RANK, compareMarks } from \"./rank.js\";\n\nexport const MANIFEST_URL = \"https://sdv.nyc3.cdn.digitaloceanspaces.com/assets/public/manifest/marks.csv\";\n\nexport interface ManifestRow {\n level: string;\n league: string;\n entity_id: string;\n mark_type: string;\n variant: string;\n valid_from: string;\n valid_to: string;\n source: string;\n sha256: string;\n ext: string;\n width: string;\n height: string;\n archive_url: string;\n first_seen: string;\n}\n\n/** archive_url reaches the web adapters' HTML (Python drops rows failing SAFE_URL); a row must carry exactly the content-addressed URL the loader derives, or it is dropped. */\nexport const safeArchive = (m: Pick<ManifestRow, \"archive_url\" | \"sha256\" | \"ext\">): boolean =>\n /^[0-9a-f]{64}$/.test(m.sha256) &&\n /^[a-z0-9]+$/.test(m.ext) &&\n m.archive_url === archiveUrl(m.sha256, m.ext);\n\n/** RFC 4180 over the whole text (quoted commas, doubled quotes, embedded newlines; CRLF or LF); rows keyed by header name. */\nexport function parseManifestCsv(text: string): ManifestRow[] {\n const src = text.replace(/^\\uFEFF/, \"\"); // a BOM would make the first header \"\\uFEFFlevel\" and drop every row\n const records: string[][] = [];\n let rec: string[] = [];\n let cur = \"\";\n let q = false;\n const endRecord = (): void => {\n rec.push(cur);\n cur = \"\";\n if (rec.length > 1 || rec[0] !== \"\") records.push(rec);\n rec = [];\n };\n for (let i = 0; i < src.length; i++) {\n const c = src[i] as string;\n if (q) {\n if (c !== '\"') cur += c;\n else if (src[i + 1] === '\"') {\n cur += '\"';\n i++;\n } else q = false;\n } else if (c === '\"') q = true;\n else if (c === \",\") {\n rec.push(cur);\n cur = \"\";\n } else if (c === \"\\n\") endRecord();\n else if (c === \"\\r\" && src[i + 1] === \"\\n\") continue;\n else cur += c;\n }\n if (cur !== \"\" || rec.length > 0) endRecord();\n const header = records[0] ?? [];\n return records.slice(1).map((f) => {\n const r: Record<string, string> = {};\n header.forEach((h, i) => {\n r[h] = f[i] ?? \"\";\n });\n return r as unknown as ManifestRow;\n });\n}\n\nconst int = (s: string): number | null => (s === \"\" ? null : Number.parseInt(s, 10));\nconst key = (source: string, id: string): string => `${source}:${id}`.trim().toLowerCase();\n\n/** mark aliases: key → (team_id, union range), unique team mappings only. */\nexport function markAliases(\n aliases: readonly Alias[],\n): Map<string, { team_id: string; from: number | null; to: number | null }> {\n const by = new Map<\n string,\n { teams: Set<string>; from: number | null; to: number | null; anyOpenFrom: boolean; anyOpenTo: boolean }\n >();\n for (const a of aliases) {\n if (a.id_system !== \"mark\") continue;\n const k = a.value.trim().toLowerCase();\n const e = by.get(k) ?? {\n teams: new Set<string>(),\n from: null,\n to: null,\n anyOpenFrom: false,\n anyOpenTo: false,\n };\n e.teams.add(a.team_id);\n if (a.valid_from === null) e.anyOpenFrom = true;\n else e.from = e.from === null ? a.valid_from : Math.min(e.from, a.valid_from);\n if (a.valid_to === null) e.anyOpenTo = true;\n else e.to = e.to === null ? a.valid_to : Math.max(e.to, a.valid_to);\n by.set(k, e);\n }\n const out = new Map<string, { team_id: string; from: number | null; to: number | null }>();\n for (const [k, e] of by)\n if (e.teams.size === 1)\n out.set(k, {\n team_id: [...e.teams][0] as string,\n from: e.anyOpenFrom ? null : e.from,\n to: e.anyOpenTo ? null : e.to,\n });\n return out;\n}\n\n/** Alias-mapped, safe-URL-filtered, ranked marks of one league; `archive_url` is derived, never copied. Not deduped. */\nexport function manifestMarks(\n league: string,\n rows: readonly ManifestRow[],\n aliases: readonly Alias[],\n): MarkRow[] {\n const ma = markAliases(aliases);\n const out: MarkRow[] = [];\n for (const m of rows) {\n if (m.level !== \"team\" || m.league !== league || !safeArchive(m)) continue;\n const a = ma.get(key(m.source, m.entity_id));\n if (!a) continue;\n const vf = int(m.valid_from);\n const vt = int(m.valid_to);\n out.push({\n team_id: a.team_id,\n mark_type: m.mark_type as \"logo\" | \"wordmark\",\n variant: m.variant,\n valid_from: vf === null ? a.from : a.from === null ? vf : Math.max(vf, a.from), // pl.max_horizontal (nulls ignored)\n valid_to: vt === null ? a.to : a.to === null ? vt : Math.min(vt, a.to), // pl.min_horizontal\n source: m.source,\n first_seen: m.first_seen,\n sha256: m.sha256,\n ext: m.ext,\n width: int(m.width),\n height: int(m.height),\n source_rank: SOURCE_RANK[m.source] ?? 5, // after height: the shard generator's JSON key order\n archive_url: archiveUrl(m.sha256, m.ext),\n });\n }\n return out.sort(compareMarks);\n}\n\nlet cached: Promise<ManifestRow[]> | undefined;\nexport function resetManifestCache(): void {\n cached = undefined;\n}\n/** The CDN manifest, fetched once per process (a failure is not cached). */\nexport function fetchManifest(o: { fetch?: typeof fetch } = {}): Promise<ManifestRow[]> {\n const f = o.fetch ?? globalThis.fetch;\n cached ??= (async () => {\n const r = await f(MANIFEST_URL);\n if (!r.ok)\n throw new DownloadError(\n `manifest download failed: ${MANIFEST_URL} answered ${r.status}`,\n MANIFEST_URL,\n r.status,\n );\n return parseManifestCsv(await r.text());\n })().catch((e: unknown) => {\n cached = undefined;\n throw e;\n });\n return cached;\n}\n","import { VARIANTS } from \"./data/index.js\";\nimport type { League, LeagueData, MarkRow } from \"./data/index.js\";\nimport { InputError, UnresolvedTeamError, warn } from \"./errors.js\";\nimport { getLeagueSync, loadLeague } from \"./index-data.js\";\nimport { fetchManifest, manifestMarks } from \"./manifest.js\";\nimport { normSeason } from \"./normalize.js\";\nimport { type ResolveOptions, type Value, resolveSync } from \"./resolve.js\";\nimport type { IdSystem, MarkType, SeasonInput, Variant } from \"./types.js\";\n\nexport interface SelectOptions {\n season?: SeasonInput;\n variant?: Variant;\n markType?: MarkType;\n}\nexport interface LogoUrlOptions extends SelectOptions {\n idSystem?: IdSystem;\n strict?: boolean;\n}\n\n/** Drops undefined values (exactOptionalPropertyTypes). */\nconst ro = (o: {\n season?: SeasonInput;\n idSystem?: IdSystem | undefined;\n strict?: boolean | undefined;\n}): ResolveOptions => Object.fromEntries(Object.entries(o).filter(([, v]) => v !== undefined));\n\nconst repr = (v: Value): string => (typeof v === \"string\" ? `'${v}'` : String(v));\n\nconst covers = (lo: number | null, hi: number | null, s: number): boolean =>\n (lo === null || s >= lo) && (hi === null || s <= hi);\n\nfunction checkMarkType(markType: string): void {\n if (markType !== \"logo\" && markType !== \"wordmark\")\n throw new InputError(`markType must be \"logo\" or \"wordmark\", got ${JSON.stringify(markType)}`);\n}\nfunction checkVariant(variant: string, league: League, d: LeagueData): void {\n if (variant === \"default\" || variant === \"dark\") return;\n if ((VARIANTS as readonly string[]).includes(variant)) return;\n const known = [...new Set(d.marks.map((m) => m.variant))].sort();\n throw new InputError(\n `unknown variant ${JSON.stringify(variant)} for ${league}; use \"default\", \"dark\" or one of ${known.join(\", \")}`,\n );\n}\n\n/** Port of `select_mark` (post-preload): requested variant, then (keeping polarity) default / polarity variants, then any; within each, season-covering rows (dated before open-ended), else the best row. Rows are already ranked best-first. */\nexport function selectMarkSync(team: Value, league: League, o: SelectOptions = {}): MarkRow | undefined {\n const markType = o.markType ?? \"logo\";\n const variant = o.variant ?? \"default\";\n checkMarkType(markType);\n const s = normSeason(o.season, league);\n const d = getLeagueSync(league);\n checkVariant(variant, league, d);\n const teamId = resolveSync(team, league, { season: s });\n if (teamId === undefined) return undefined;\n const rows = d.marks.filter((r) => r.team_id === teamId && r.mark_type === markType);\n const side = variant === \"dark\" ? \"dark\" : \"light\";\n const polarity = (v: string): boolean => v === `on_${side}` || v.endsWith(`_on_${side}`);\n const requested = (v: string): boolean => v === variant;\n const dflt = (v: string): boolean => v === \"default\";\n const order = side === \"dark\" ? [requested, polarity, dflt] : [requested, dflt, polarity];\n for (const keep of [...order, null]) {\n const found = keep ? rows.filter((r) => keep(r.variant)) : rows;\n if (s !== null) {\n const covering = found.filter((r) => covers(r.valid_from, r.valid_to, s));\n const dated = covering.filter((r) => r.valid_from !== null || r.valid_to !== null);\n for (const set of [dated, covering]) if (set.length) return set[0];\n }\n if (found.length) return found[0];\n }\n return undefined;\n}\nexport async function selectMark(\n team: Value,\n league: League,\n o: SelectOptions = {},\n): Promise<MarkRow | undefined> {\n await loadLeague(league);\n return selectMarkSync(team, league, o);\n}\n\n/** Port of `logo_url` (post-preload): archive URL of the team's logo/wordmark for the season; `undefined` for an unknown team (the resolver warns) or when no mark of that type exists (one warning). */\nexport function logoUrlSync(team: Value, league: League, o: LogoUrlOptions = {}): string | undefined {\n const markType = o.markType ?? \"logo\";\n checkMarkType(markType);\n checkVariant(o.variant ?? \"default\", league, getLeagueSync(league));\n const teamId = resolveSync(team, league, ro({ season: o.season, idSystem: o.idSystem, strict: o.strict }));\n if (teamId === undefined) return undefined;\n const row = selectMarkSync(teamId, league, o);\n if (row === undefined) {\n warn(`marks:${league}:${teamId}:${markType}`, `no ${markType} archived for ${repr(team)} (${league})`);\n return undefined;\n }\n return row.archive_url;\n}\n/**\n * Archive URL of a team's logo (or wordmark, `markType`) for the season, loading the league first; `undefined` for an\n * unknown team (the resolver warns) or when no mark of that type is archived (one warning).\n *\n * @example\n * ```ts\n * import { logoUrl } from \"@sportsdataverse/sdvplot\";\n *\n * await logoUrl(\"KC\", \"nfl\", { markType: \"wordmark\" });\n * ```\n */\nexport async function logoUrl(\n team: Value,\n league: League,\n o: LogoUrlOptions = {},\n): Promise<string | undefined> {\n await loadLeague(league);\n return logoUrlSync(team, league, o);\n}\n\n/**\n * Every archived mark for one team, best first (logos and wordmarks, every variant and source).\n * Default: the committed shard (deduped, ranked rows). `full: true` fetches the CDN manifest lazily (once per process)\n * and returns every manifest row for the team, including rows the shard dedupe dropped; a failed download throws\n * `DownloadError`. Both paths filter on the team only. `fetch` overrides the global fetch for the manifest download.\n * Throws `UnresolvedTeamError` when the team is null or does not resolve (strict, as Python).\n */\nexport async function marks(\n team: Value,\n league: League,\n o: { season?: SeasonInput; idSystem?: IdSystem; full?: boolean; fetch?: typeof fetch } = {},\n): Promise<readonly MarkRow[]> {\n const d = await loadLeague(league);\n const teamId = resolveSync(team, league, ro({ season: o.season, idSystem: o.idSystem, strict: true }));\n if (teamId === undefined) throw new UnresolvedTeamError(`marks() needs a team, got ${String(team)}`);\n if (!o.full) return d.marks.filter((r) => r.team_id === teamId);\n const fo = o.fetch === undefined ? {} : { fetch: o.fetch };\n return manifestMarks(league, await fetchManifest(fo), d.aliases).filter((r) => r.team_id === teamId);\n}\n","import type { MarkRow } from \"./data/index.js\";\nimport { InputError, warn } from \"./errors.js\";\nimport { ESPN_HEADSHOT_LEAGUES, HEADSHOT_ASPECT, headshotUrl, loadGsis } from \"./headshots.js\";\nimport type { EspnHeadshotLeague } from \"./headshots.js\";\nimport { getLeagueSync, loadLeague } from \"./index-data.js\";\nimport { selectMarkSync } from \"./marks.js\";\nimport { normValue } from \"./normalize.js\";\nimport { type ResolveOptions, type Value, resolveSync } from \"./resolve.js\";\nimport type { HeadshotIdSystem, IdSystem, League, SeasonInput, Variant } from \"./types.js\";\n\nexport type Kind = \"logo\" | \"wordmark\" | \"headshot\";\n/** One point to draw: the caller's own x/y, the image URL and its width / height ratio (null when unknown). `index` is the row's position in the input arrays (skipped rows leave gaps). */\nexport interface Placement {\n index: number;\n id: string;\n x: Value;\n y: Value;\n url: string;\n aspect: number | null;\n mark: MarkRow | null;\n}\nexport interface PlaceOptions {\n league: League;\n season?: SeasonInput | readonly SeasonInput[];\n kind?: Kind;\n variant?: Variant;\n /** Team id system for logos/wordmarks (default \"auto\"); \"espn\" or \"gsis\" for headshots (default \"espn\"). */\n idSystem?: IdSystem | HeadshotIdSystem;\n strict?: boolean;\n warn?: boolean;\n}\nexport { HEADSHOT_ASPECT };\n\n/**\n * What a placed image is named for (its accessible name, before the mark type): a logo's or wordmark's resolved team,\n * by abbreviation, else full name, whatever id system the caller keyed it by; `raw` (the caller's own value) only when\n * the team has neither. A headshot has no name source (no team row; the gsis map holds ids only), so it keeps `raw`.\n * Every renderer that names images goes through this one rule.\n * @internal\n */\nexport function placedName(p: Placement, league: League, raw: unknown): string {\n // ponytail: a linear scan of the league's teams per image (2,631 for soccer at most); index by team_id if it shows\n const team = p.mark === null ? undefined : getLeagueSync(league).teams.find((t) => t.team_id === p.id);\n return team?.abbr || team?.name || String(raw);\n}\n\nconst real = (v: unknown): v is number => typeof v === \"number\" && !Number.isNaN(v);\nexport function checkHeight(height: unknown): number {\n if (!real(height) || !(height > 0 && height <= 1))\n throw new InputError(`height is a fraction of the plot height in (0, 1], got ${String(height)}`);\n return height;\n}\nexport function checkAlpha(alpha: unknown): number {\n if (!real(alpha) || !(alpha >= 0 && alpha <= 1))\n throw new InputError(`alpha is an opacity in [0, 1], got ${String(alpha)}`);\n return alpha;\n}\n\nconst missing = (v: Value): boolean =>\n v === null || v === undefined || (typeof v === \"number\" && Number.isNaN(v));\nfunction skipped(league: string, reason: string, values: Value[], on: boolean): void {\n if (!on || values.length === 0) return;\n const extra = values.length > 10 ? ` and ${values.length - 10} more` : \"\";\n const shown = `${values.slice(0, 10).map(String).join(\", \")}${extra}`;\n warn(`place:${league}:${reason}:${shown}`, `skipped ${values.length} point(s) ${reason}: ${shown}`);\n}\n\n/** Port of `_placement.place` (post-preload): pairs each (x, y) with its team's mark or player's headshot, dropping and warning (once per reason) on rows that cannot be drawn. Needs `loadLeague(league)` (or `preloadAll()`) first; gsis headshots also need `loadGsis()`. */\nexport function placeSync(\n xs: readonly Value[],\n ys: readonly Value[],\n teams: readonly Value[],\n o: PlaceOptions,\n): Placement[] {\n const { league, kind = \"logo\", variant = \"default\", strict = false, warn: on = true } = o;\n if (!(xs.length === ys.length && ys.length === teams.length))\n throw new InputError(\n `x, y and teams must have the same length, got ${xs.length}, ${ys.length} and ${teams.length}`,\n );\n const out: Placement[] = [];\n const missingXY: Value[] = [];\n if (kind === \"headshot\") {\n if (!Object.hasOwn(ESPN_HEADSHOT_LEAGUES, league))\n throw new InputError(\n `no ESPN headshots for league ${JSON.stringify(league)}; supported: ${Object.keys(ESPN_HEADSHOT_LEAGUES).sort().join(\", \")}`,\n );\n const hl = league as EspnHeadshotLeague;\n const gsis = o.idSystem === \"gsis\";\n const noImage: Value[] = [];\n teams.forEach((pid, i) => {\n if (missing(pid)) return;\n if (missing(xs[i]) || missing(ys[i])) {\n missingXY.push(pid);\n return;\n }\n const url = headshotUrl(pid, hl, { idSystem: gsis ? \"gsis\" : \"espn\" });\n if (url === undefined) {\n noImage.push(pid);\n return;\n }\n const id = gsis ? String(pid).trim() : (normValue(pid) ?? String(pid).trim());\n out.push({ index: i, id, x: xs[i], y: ys[i], url, aspect: HEADSHOT_ASPECT, mark: null });\n });\n skipped(league, \"with no headshot\", noImage, on);\n } else {\n const seasons: readonly SeasonInput[] = Array.isArray(o.season)\n ? (o.season as readonly SeasonInput[])\n : teams.map(() => o.season as SeasonInput);\n const ropts: ResolveOptions = { season: seasons, strict };\n if (o.idSystem !== undefined) ropts.idSystem = o.idSystem as IdSystem;\n const ids = resolveSync(teams, league, ropts);\n const cache = new Map<string, MarkRow | undefined>();\n const noMark: Value[] = [];\n teams.forEach((raw, i) => {\n const id = ids[i];\n if (id === undefined) return; // the resolver already warned (once per call)\n if (missing(xs[i]) || missing(ys[i])) {\n missingXY.push(raw);\n return;\n }\n const k = `${id}|${String(seasons[i])}`;\n if (!cache.has(k))\n cache.set(k, selectMarkSync(id, league, { season: seasons[i], variant, markType: kind }));\n const row = cache.get(k);\n if (!row) {\n noMark.push(raw);\n return;\n }\n const aspect = row.width && row.height ? row.width / row.height : null;\n out.push({ index: i, id, x: xs[i], y: ys[i], url: row.archive_url, aspect, mark: row });\n });\n skipped(league, `with no ${kind} archived`, noMark, on);\n }\n skipped(league, \"with a missing x or y\", missingXY, on);\n return out;\n}\nexport async function place(\n xs: readonly Value[],\n ys: readonly Value[],\n teams: readonly Value[],\n o: PlaceOptions,\n): Promise<Placement[]> {\n await loadLeague(o.league);\n if (o.kind === \"headshot\" && o.idSystem === \"gsis\") await loadGsis();\n return placeSync(xs, ys, teams, o);\n}\n"]}
@@ -0,0 +1,120 @@
1
+ import { colorList, teamPalette } from './chunk-UL5M6KDE.js';
2
+ import { resolveSync } from './chunk-ZAEQXFJM.js';
3
+ import { hex6 } from './chunk-J6R2LEJ7.js';
4
+ import { InputError } from './chunk-E7G7VSLV.js';
5
+
6
+ // src/three/tube.ts
7
+ function teamColorPalette(league, teams, o = {}) {
8
+ const fallback = hex6(o.fallback ?? "#808080");
9
+ const season = o.season === void 0 ? {} : { season: o.season };
10
+ const ids = resolveSync(teams, league, {
11
+ ...season,
12
+ ...o.idSystem === void 0 ? {} : { idSystem: o.idSystem }
13
+ });
14
+ const known = [...new Set(ids.filter((id) => id !== void 0 && id !== null))];
15
+ const which = o.which ?? "primary";
16
+ const co = { ...season, idSystem: "team_id" };
17
+ const main = colorList(league, known, { ...co, which });
18
+ const other = colorList(league, known, { ...co, which: which === "primary" ? "secondary" : "primary" });
19
+ let colors = main.map((c, i) => hex6(c ?? other[i] ?? fallback, { dropAlpha: true }));
20
+ if (o.background !== void 0) colors = teamPalette(colors, other, hex6(o.background), known);
21
+ const by = new Map(known.map((id, i) => [id, colors[i]]));
22
+ return ids.map((id) => id === void 0 ? fallback : by.get(id));
23
+ }
24
+ var real = (v) => typeof v === "number" && Number.isFinite(v);
25
+ var minus = (a, b) => [a[0] - b[0], a[1] - b[1], a[2] - b[2]];
26
+ var cross = (a, b) => [
27
+ a[1] * b[2] - a[2] * b[1],
28
+ a[2] * b[0] - a[0] * b[2],
29
+ a[0] * b[1] - a[1] * b[0]
30
+ ];
31
+ var dot = (a, b) => a[0] * b[0] + a[1] * b[1] + a[2] * b[2];
32
+ var unit = (a) => {
33
+ const n = Math.sqrt(dot(a, a));
34
+ return [a[0] / n, a[1] / n, a[2] / n];
35
+ };
36
+ function trajectoryTube(points, o) {
37
+ for (const k of ["width", "size", "lineWidth", "height"])
38
+ if (k in o)
39
+ throw new InputError(
40
+ `trajectoryTube takes no ${k}: three.js draws in world units, so give radius in the points' own units, not px`
41
+ );
42
+ if (!(real(o.radius) && o.radius > 0))
43
+ throw new InputError(
44
+ `radius is the tube's radius in the points' world units, greater than 0, got ${String(o.radius)}`
45
+ );
46
+ const segments = o.radialSegments ?? 12;
47
+ if (!(Number.isInteger(segments) && segments >= 3))
48
+ throw new InputError(`radialSegments is a whole number, 3 or more, got ${String(segments)}`);
49
+ if (o.color === void 0 === (o.team === void 0))
50
+ throw new InputError("trajectoryTube takes a color or team (with league), one of the two");
51
+ let color;
52
+ if (o.color !== void 0) color = hex6(o.color);
53
+ else {
54
+ if (o.league === void 0) throw new InputError("trajectoryTube's team needs its league");
55
+ const { radius: _r, radialSegments: _s, color: _c, team: _t, league: _l, ...co } = o;
56
+ color = teamColorPalette(o.league, [o.team], co)[0];
57
+ }
58
+ const path = [];
59
+ for (const p of points) {
60
+ if (!(p.length === 3 && p.every(real)))
61
+ throw new InputError(`each point is [x, y, z], three finite numbers, got ${JSON.stringify(p)}`);
62
+ const last = path[path.length - 1];
63
+ if (last === void 0 || last[0] !== p[0] || last[1] !== p[1] || last[2] !== p[2]) path.push(p);
64
+ }
65
+ if (path.length < 2) throw new InputError("trajectoryTube needs at least two different points");
66
+ const n = path.length;
67
+ const tangents = path.map((p, k) => {
68
+ const d = minus(path[Math.min(k + 1, n - 1)], path[Math.max(k - 1, 0)]);
69
+ if (dot(d, d) === 0)
70
+ throw new InputError(
71
+ `trajectoryTube: the path doubles back at point ${k} (${p.join(", ")}): its neighbours are the same point`
72
+ );
73
+ return unit(d);
74
+ });
75
+ const t0 = tangents[0];
76
+ const ax = Math.abs(t0[0]) <= Math.abs(t0[1]) && Math.abs(t0[0]) <= Math.abs(t0[2]) ? 0 : Math.abs(t0[1]) <= Math.abs(t0[2]) ? 1 : 2;
77
+ let normal = unit(cross(t0, [ax === 0 ? 1 : 0, ax === 1 ? 1 : 0, ax === 2 ? 1 : 0]));
78
+ const positions = new Float32Array(n * segments * 3);
79
+ const normals = new Float32Array(n * segments * 3);
80
+ let length = 0;
81
+ path.forEach((p, k) => {
82
+ const t = tangents[k];
83
+ if (k > 0) {
84
+ length += Math.sqrt(dot(minus(p, path[k - 1]), minus(p, path[k - 1])));
85
+ const along = dot(normal, t);
86
+ const carried = [normal[0] - along * t[0], normal[1] - along * t[1], normal[2] - along * t[2]];
87
+ if (dot(carried, carried) > 1e-12) normal = unit(carried);
88
+ }
89
+ const binormal = cross(t, normal);
90
+ for (let j = 0; j < segments; j++) {
91
+ const a = 2 * Math.PI * j / segments;
92
+ const d = [
93
+ Math.cos(a) * normal[0] + Math.sin(a) * binormal[0],
94
+ Math.cos(a) * normal[1] + Math.sin(a) * binormal[1],
95
+ Math.cos(a) * normal[2] + Math.sin(a) * binormal[2]
96
+ ];
97
+ const v = 3 * (k * segments + j);
98
+ for (let c = 0; c < 3; c++) {
99
+ positions[v + c] = p[c] + o.radius * d[c];
100
+ normals[v + c] = d[c];
101
+ }
102
+ }
103
+ });
104
+ const indices = new Uint32Array((n - 1) * segments * 6);
105
+ let i = 0;
106
+ for (let k = 0; k < n - 1; k++)
107
+ for (let j = 0; j < segments; j++) {
108
+ const a = k * segments + j;
109
+ const b = k * segments + (j + 1) % segments;
110
+ const c = a + segments;
111
+ const d = b + segments;
112
+ indices.set([a, b, c, b, d, c], i);
113
+ i += 6;
114
+ }
115
+ return { positions, normals, indices, color, radius: o.radius, length };
116
+ }
117
+
118
+ export { teamColorPalette, trajectoryTube };
119
+ //# sourceMappingURL=chunk-AMXASQHT.js.map
120
+ //# sourceMappingURL=chunk-AMXASQHT.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/three/tube.ts"],"names":[],"mappings":"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","file":"chunk-AMXASQHT.js","sourcesContent":["import { colorList } from \"../_web.js\";\nimport { hex6 } from \"../contrast.js\";\nimport { InputError } from \"../errors.js\";\nimport { type Value, resolveSync } from \"../resolve.js\";\nimport { teamPalette } from \"../team-palette.js\";\nimport type { IdSystem, League, SeasonInput, TeamId, Which } from \"../types.js\";\n\n/** `teamColorPalette` options. */\nexport interface ThreeColorOptions {\n /** \"primary\" (default) or \"secondary\": the team colour each team gets. */\n which?: Which;\n season?: SeasonInput;\n idSystem?: IdSystem;\n /** The colour of a team that does not resolve. Default \"#808080\". */\n fallback?: string;\n /**\n * The scene's background (what the colours are seen against, such as the surface under them). Given, a colour under\n * 3:1 contrast against it is swapped for the team's other colour, else moved towards white (on a dark background)\n * or black (a light one) until it reads; a colour that would then sit too close to an earlier team's (CIE76 ΔE under\n * 20) takes the team's other colour, or the nearest colour to its own that is distinct and still reads; failing that\n * it is kept, with one warning. A team that does not resolve keeps `fallback` and stays out of that search.\n */\n background?: string;\n}\n\n/**\n * One colour per team, in order, for each team's mesh material (a `Trajectory`'s `color`, a `MeshBasicMaterial`'s\n * `color`). With `background`, every colour keeps 3:1 contrast against it (dark mode) and the teams aim to stay\n * distinct (when they cannot, it keeps the readable colour and warns once). A team listed twice gets one colour.\n *\n * @example\n * ```ts\n * import { loadLeague } from \"@sportsdataverse/sdvplot\";\n * import { teamColorPalette } from \"@sportsdataverse/sdvplot/three\";\n *\n * await loadLeague(\"mlb\");\n * teamColorPalette(\"mlb\", [\"NYY\", \"CWS\"], { background: \"#395d33\" }); // readable on sporty's baseball grass\n * ```\n */\nexport function teamColorPalette(\n league: League,\n teams: readonly unknown[],\n o: ThreeColorOptions = {},\n): string[] {\n const fallback = hex6(o.fallback ?? \"#808080\");\n const season = o.season === undefined ? {} : { season: o.season };\n const ids = resolveSync(teams as readonly Value[], league, {\n ...season,\n ...(o.idSystem === undefined ? {} : { idSystem: o.idSystem }),\n });\n const known: string[] = [...new Set(ids.filter((id): id is TeamId => id !== undefined && id !== null))];\n const which = o.which ?? \"primary\";\n const co = { ...season, idSystem: \"team_id\" as const };\n const main = colorList(league, known, { ...co, which });\n const other = colorList(league, known, { ...co, which: which === \"primary\" ? \"secondary\" : \"primary\" });\n // a team with no `which` colour (often no secondary) takes its other colour, then the fallback\n let colors = main.map((c, i) => hex6(c ?? other[i] ?? fallback, { dropAlpha: true }));\n if (o.background !== undefined) colors = teamPalette(colors, other, hex6(o.background), known);\n const by = new Map(known.map((id, i) => [id, colors[i] as string]));\n return ids.map((id) => (id === undefined ? fallback : (by.get(id) as string)));\n}\n\n/** A point in world units: `[x, y, z]`, z up. */\nexport type Point3 = readonly [x: number, y: number, z: number];\ntype P3 = Point3;\n/** `trajectoryTube` options: the radius in world units, and either a colour or a team. */\nexport interface TrajectoryTubeOptions extends Omit<ThreeColorOptions, \"fallback\"> {\n /**\n * The tube's radius in world units: the points' own units (feet for a pitch over a baseball field), not px. Three.js\n * draws in the scene's units, so a tube is as thick as this whatever the camera; a baseball's radius is 0.121 ft.\n */\n radius: number;\n /** Vertices round each ring, 3 or more. Default 12. */\n radialSegments?: number;\n /** The tube's colour, `#rrggbb`. Give this or `team` and `league`. */\n color?: string;\n /** The team whose colour the tube takes (abbreviation, name or id), with `league`. */\n team?: unknown;\n league?: League;\n /** The colour of a team that does not resolve. Default \"#808080\". */\n fallback?: string;\n}\n/** A tube mesh, ready for a three.js `BufferGeometry` (world units). */\nexport interface TubeMesh {\n /** x, y, z per vertex: `radialSegments` vertices round each point of the path, ring after ring. */\n positions: Float32Array;\n /** The outward unit normal of each vertex. */\n normals: Float32Array;\n /** Three vertex indices per triangle, counter-clockwise seen from outside the tube. */\n indices: Uint32Array;\n /** The tube's colour, `#rrggbb` (sRGB, as a three.js `Color` takes it). */\n color: string;\n radius: number;\n /** The path's length, world units. */\n length: number;\n}\n\nconst real = (v: unknown): v is number => typeof v === \"number\" && Number.isFinite(v);\nconst minus = (a: P3, b: P3): P3 => [a[0] - b[0], a[1] - b[1], a[2] - b[2]];\nconst cross = (a: P3, b: P3): P3 => [\n a[1] * b[2] - a[2] * b[1],\n a[2] * b[0] - a[0] * b[2],\n a[0] * b[1] - a[1] * b[0],\n];\nconst dot = (a: P3, b: P3): number => a[0] * b[0] + a[1] * b[1] + a[2] * b[2];\nconst unit = (a: P3): P3 => {\n const n = Math.sqrt(dot(a, a));\n return [a[0] / n, a[1] / n, a[2] / n];\n};\n\n/**\n * A tube along a 3D polyline (a pitch, a ball flight, a skater's path) as plain mesh buffers, in the points' own world\n * units: ready for `new BufferGeometry()` in three.js, or the `<Trajectory>` component in `sdvplot/react` for\n * react-three-fiber. The tube is `radius` round (world units, not px), one ring of `radialSegments` vertices at each\n * point, the rings turned along the path without twisting (each ring's frame is the last one's, carried along: a\n * parallel-transport frame), with open ends. Its colour is `color`, or the team's (`team` and `league`; `background`\n * makes it read against the scene, as `teamColorPalette` does); draw it with that colour unlit and not tone-mapped\n * (`MeshBasicMaterial`, `toneMapped: false`) to show the team's colour as it is. A repeated point is skipped.\n *\n * @example\n * ```ts\n * import { loadLeague } from \"@sportsdataverse/sdvplot\";\n * import { statcastPitchPath, trajectoryTube } from \"@sportsdataverse/sdvplot/three\";\n *\n * await loadLeague(\"mlb\");\n * // a 96.3 mph four-seamer, Yankees at Red Sox, 2024-06-15 (Baseball Savant: game 746946, at-bat 48, pitch 4)\n * const pitch = {\n * vx0: -6.245145230392043, vy0: -140.0548111003631, vz0: -4.647911601289857,\n * ax: 12.681580763686934, ay: 32.15363682007945, az: -12.428443024407864,\n * plate_x: 0.42927397039380755, plate_z: 3.546869816011372, release_pos_y: 54.12, game_year: 2024,\n * };\n * const tube = trajectoryTube(statcastPitchPath(pitch), { radius: 0.121, team: \"NYY\", league: \"mlb\" });\n * // in three.js: geometry.setAttribute(\"position\", new BufferAttribute(tube.positions, 3)), and so on\n * tube.color;\n * ```\n */\nexport function trajectoryTube(points: readonly Point3[], o: TrajectoryTubeOptions): TubeMesh {\n for (const k of [\"width\", \"size\", \"lineWidth\", \"height\"])\n if (k in o)\n throw new InputError(\n `trajectoryTube takes no ${k}: three.js draws in world units, so give radius in the points' own units, not px`,\n );\n if (!(real(o.radius) && o.radius > 0))\n throw new InputError(\n `radius is the tube's radius in the points' world units, greater than 0, got ${String(o.radius)}`,\n );\n const segments = o.radialSegments ?? 12;\n if (!(Number.isInteger(segments) && segments >= 3))\n throw new InputError(`radialSegments is a whole number, 3 or more, got ${String(segments)}`);\n if ((o.color === undefined) === (o.team === undefined))\n throw new InputError(\"trajectoryTube takes a color or team (with league), one of the two\");\n let color: string;\n if (o.color !== undefined) color = hex6(o.color);\n else {\n if (o.league === undefined) throw new InputError(\"trajectoryTube's team needs its league\");\n const { radius: _r, radialSegments: _s, color: _c, team: _t, league: _l, ...co } = o;\n color = teamColorPalette(o.league, [o.team], co)[0] as string;\n }\n\n const path: P3[] = [];\n for (const p of points) {\n if (!(p.length === 3 && p.every(real)))\n throw new InputError(`each point is [x, y, z], three finite numbers, got ${JSON.stringify(p)}`);\n const last = path[path.length - 1];\n if (last === undefined || last[0] !== p[0] || last[1] !== p[1] || last[2] !== p[2]) path.push(p);\n }\n if (path.length < 2) throw new InputError(\"trajectoryTube needs at least two different points\");\n\n const n = path.length;\n const tangents = path.map((p, k) => {\n const d = minus(path[Math.min(k + 1, n - 1)] as P3, path[Math.max(k - 1, 0)] as P3);\n // a path that doubles back (A, B, A) has no direction at B: no ring can be squared to it\n if (dot(d, d) === 0)\n throw new InputError(\n `trajectoryTube: the path doubles back at point ${k} (${p.join(\", \")}): its neighbours are the same point`,\n );\n return unit(d);\n });\n // the first ring's frame: a normal square to the first tangent, from the axis it leans on least\n const t0 = tangents[0] as P3;\n const ax =\n Math.abs(t0[0]) <= Math.abs(t0[1]) && Math.abs(t0[0]) <= Math.abs(t0[2])\n ? 0\n : Math.abs(t0[1]) <= Math.abs(t0[2])\n ? 1\n : 2;\n let normal = unit(cross(t0, [ax === 0 ? 1 : 0, ax === 1 ? 1 : 0, ax === 2 ? 1 : 0]));\n const positions = new Float32Array(n * segments * 3);\n const normals = new Float32Array(n * segments * 3);\n let length = 0;\n path.forEach((p, k) => {\n const t = tangents[k] as P3;\n if (k > 0) {\n length += Math.sqrt(dot(minus(p, path[k - 1] as P3), minus(p, path[k - 1] as P3)));\n // carry the last normal along: take out its part along the new tangent (no twist about the path)\n const along = dot(normal, t);\n const carried: P3 = [normal[0] - along * t[0], normal[1] - along * t[1], normal[2] - along * t[2]];\n // ponytail: a turn of 90 degrees or more between two points loses the frame; a pitch or ball flight never does\n if (dot(carried, carried) > 1e-12) normal = unit(carried);\n }\n const binormal = cross(t, normal);\n for (let j = 0; j < segments; j++) {\n const a = (2 * Math.PI * j) / segments;\n const d: P3 = [\n Math.cos(a) * normal[0] + Math.sin(a) * binormal[0],\n Math.cos(a) * normal[1] + Math.sin(a) * binormal[1],\n Math.cos(a) * normal[2] + Math.sin(a) * binormal[2],\n ];\n const v = 3 * (k * segments + j);\n for (let c = 0; c < 3; c++) {\n positions[v + c] = (p[c] as number) + o.radius * (d[c] as number);\n normals[v + c] = d[c] as number;\n }\n }\n });\n const indices = new Uint32Array((n - 1) * segments * 6);\n let i = 0;\n for (let k = 0; k < n - 1; k++)\n for (let j = 0; j < segments; j++) {\n const a = k * segments + j;\n const b = k * segments + ((j + 1) % segments);\n const c = a + segments;\n const d = b + segments;\n indices.set([a, b, c, b, d, c], i);\n i += 6;\n }\n return { positions, normals, indices, color, radius: o.radius, length };\n}\n"]}