@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.
- package/LICENSE +21 -0
- package/NOTICE.md +109 -0
- package/README.md +984 -2
- package/dist/_web-D7Aa5Rd8.d.ts +69 -0
- package/dist/aaf-GJ3SH34T.js +13 -0
- package/dist/aaf-HCYN5T6Y.js +37 -0
- package/dist/aaf-ZAE5KS2Y.js +13 -0
- package/dist/ahl-E66MDVCU.js +66 -0
- package/dist/ahl-NPQQWZEV.js +249 -0
- package/dist/ahl-QRDU73XX.js +309 -0
- package/dist/axis-CraWQRUJ.d.ts +49 -0
- package/dist/bins.d.ts +174 -0
- package/dist/bins.js +4 -0
- package/dist/bins.js.map +1 -0
- package/dist/cfb-F42SVS4X.js +5000 -0
- package/dist/cfb-P3SHEYRZ.js +692 -0
- package/dist/cfb-WXRZANLP.js +10226 -0
- package/dist/chartjs-surface.d.ts +42 -0
- package/dist/chartjs-surface.js +65 -0
- package/dist/chartjs-surface.js.map +1 -0
- package/dist/chartjs.d.ts +162 -0
- package/dist/chartjs.js +342 -0
- package/dist/chartjs.js.map +1 -0
- package/dist/chunk-4C3GX6QO.js +92 -0
- package/dist/chunk-4C3GX6QO.js.map +1 -0
- package/dist/chunk-57WFSI4Q.js +128 -0
- package/dist/chunk-57WFSI4Q.js.map +1 -0
- package/dist/chunk-7ULOCCFL.js +313 -0
- 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-C2CDY5OG.js +137 -0
- package/dist/chunk-C2CDY5OG.js.map +1 -0
- package/dist/chunk-DYMUVE6K.js +127 -0
- package/dist/chunk-DYMUVE6K.js.map +1 -0
- package/dist/chunk-E7G7VSLV.js +43 -0
- package/dist/chunk-E7G7VSLV.js.map +1 -0
- package/dist/chunk-H2C3FMMA.js +300 -0
- package/dist/chunk-H2C3FMMA.js.map +1 -0
- package/dist/chunk-J4P4HDBF.js +175 -0
- package/dist/chunk-J4P4HDBF.js.map +1 -0
- package/dist/chunk-J6R2LEJ7.js +58 -0
- package/dist/chunk-J6R2LEJ7.js.map +1 -0
- package/dist/chunk-M3XD2OSN.js +132 -0
- package/dist/chunk-M3XD2OSN.js.map +1 -0
- package/dist/chunk-OIILARXI.js +30 -0
- package/dist/chunk-OIILARXI.js.map +1 -0
- 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-ZAEQXFJM.js +556 -0
- package/dist/chunk-ZAEQXFJM.js.map +1 -0
- package/dist/cricket-KPCYG2HL.js +493 -0
- package/dist/cricket-NDXLE25B.js +116 -0
- package/dist/cricket-YUUF7P6M.js +160 -0
- 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 +234 -0
- package/dist/d3.js +198 -0
- package/dist/d3.js.map +1 -0
- package/dist/deckgl.d.ts +232 -0
- package/dist/deckgl.js +197 -0
- package/dist/deckgl.js.map +1 -0
- package/dist/echarts.d.ts +121 -0
- package/dist/echarts.js +228 -0
- package/dist/echarts.js.map +1 -0
- package/dist/echl-3CR4RGFC.js +241 -0
- package/dist/echl-NPC32C23.js +107 -0
- package/dist/echl-PI33DGJC.js +64 -0
- package/dist/export.d.ts +130 -0
- package/dist/export.js +258 -0
- package/dist/export.js.map +1 -0
- package/dist/g2.d.ts +251 -0
- package/dist/g2.js +102 -0
- package/dist/g2.js.map +1 -0
- package/dist/headshots-B2J_LOO8.d.ts +40 -0
- package/dist/index.d.ts +183 -0
- package/dist/index.js +9 -0
- package/dist/index.js.map +1 -0
- package/dist/interact.d.ts +567 -0
- package/dist/interact.js +809 -0
- package/dist/interact.js.map +1 -0
- 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/marks-DH-tQIP-.d.ts +75 -0
- package/dist/mbb-3TRLVW3H.js +3619 -0
- package/dist/mbb-DQUHCHZP.js +5111 -0
- package/dist/mbb-PRAIO2P5.js +371 -0
- package/dist/milb-6IQ6NCEW.js +291 -0
- package/dist/milb-EYY5MD4T.js +1752 -0
- package/dist/milb-EYZ4SZLI.js +797 -0
- package/dist/mlb-4PBUJSVQ.js +35 -0
- package/dist/mlb-FHPY3GQJ.js +701 -0
- package/dist/mlb-REVGL3N2.js +665 -0
- package/dist/mui.d.ts +338 -0
- package/dist/mui.js +184 -0
- package/dist/mui.js.map +1 -0
- package/dist/nba-ATT775GS.js +485 -0
- package/dist/nba-LKM4UHVU.js +482 -0
- package/dist/nba-YX7PXM3R.js +35 -0
- package/dist/nbagl-3WGZCZ4S.js +37 -0
- package/dist/nbagl-LP4XJRAL.js +229 -0
- package/dist/nbagl-VTTOU7LN.js +499 -0
- package/dist/ncaa_baseball-UV75LNVR.js +2670 -0
- package/dist/ncaa_baseball-VD5J5C72.js +419 -0
- package/dist/ncaa_baseball-YRBS32D6.js +4592 -0
- package/dist/ncaa_mhockey-IMYHS7QS.js +1033 -0
- package/dist/ncaa_mhockey-TXTKWHE3.js +115 -0
- package/dist/ncaa_mhockey-U3MFW5VT.js +663 -0
- package/dist/ncaa_softball-6WR6KS64.js +420 -0
- package/dist/ncaa_softball-RZCTIE56.js +2654 -0
- package/dist/ncaa_softball-VOD4PVGY.js +4599 -0
- package/dist/ncaa_whockey-6XXGUTBK.js +607 -0
- package/dist/ncaa_whockey-AKKYKY67.js +53 -0
- package/dist/ncaa_whockey-KBEKXFBB.js +293 -0
- package/dist/nfl-BDB2HYAR.js +625 -0
- package/dist/nfl-EHYHDLJD.js +37 -0
- package/dist/nfl-VF4NJCHY.js +514 -0
- package/dist/nfl_gsis-UY64JO37.js +4 -0
- package/dist/nhl-6WPHME7H.js +511 -0
- package/dist/nhl-EMIJFZHY.js +884 -0
- package/dist/nhl-ISFUKRFU.js +37 -0
- package/dist/nivo.d.ts +193 -0
- package/dist/nivo.js +8 -0
- package/dist/nivo.js.map +1 -0
- package/dist/ohl-2FEM554G.js +113 -0
- package/dist/ohl-C5Q4W6JB.js +32 -0
- package/dist/ohl-TQ4J46VC.js +66 -0
- package/dist/perspective.d.ts +198 -0
- package/dist/perspective.js +171 -0
- package/dist/perspective.js.map +1 -0
- package/dist/phf-H7POU2KW.js +32 -0
- package/dist/phf-K5IFMZUM.js +13 -0
- package/dist/phf-TWJI3BOW.js +20 -0
- package/dist/placement-UWkqUzpa.d.ts +32 -0
- package/dist/plot.d.ts +451 -0
- package/dist/plot.js +712 -0
- package/dist/plot.js.map +1 -0
- package/dist/plotly.d.ts +129 -0
- package/dist/plotly.js +360 -0
- package/dist/plotly.js.map +1 -0
- package/dist/pwhl-OX3BO4JI.js +53 -0
- package/dist/pwhl-W44OC3C6.js +17 -0
- package/dist/pwhl-ZJGG3H7W.js +23 -0
- package/dist/qmjhl-5HISEHLL.js +35 -0
- package/dist/qmjhl-DA3UHKE5.js +125 -0
- package/dist/qmjhl-YG4BOQPF.js +614 -0
- package/dist/react.d.ts +377 -0
- package/dist/react.js +522 -0
- package/dist/react.js.map +1 -0
- package/dist/recharts.d.ts +162 -0
- package/dist/recharts.js +8 -0
- package/dist/recharts.js.map +1 -0
- package/dist/resolve-CQW-jzvr.d.ts +28 -0
- package/dist/selection-D4T3PkRt.d.ts +140 -0
- package/dist/shadcn.d.ts +83 -0
- package/dist/shadcn.js +88 -0
- package/dist/shadcn.js.map +1 -0
- package/dist/shots.d.ts +364 -0
- package/dist/shots.js +86 -0
- package/dist/shots.js.map +1 -0
- package/dist/soccer-7BH3ZLVL.js +7898 -0
- package/dist/soccer-MRU25OGE.js +2636 -0
- package/dist/soccer-PF4IPPVA.js +12342 -0
- 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.d.ts +75 -0
- package/dist/testing.js +239 -0
- package/dist/testing.js.map +1 -0
- package/dist/three.d.ts +223 -0
- package/dist/three.js +50 -0
- package/dist/three.js.map +1 -0
- package/dist/tiers-BN4md-GB.d.ts +65 -0
- package/dist/types-C7n6gJF3.d.ts +62 -0
- package/dist/ufl-7VLZTLTT.js +143 -0
- package/dist/ufl-CCMBLPSW.js +100 -0
- package/dist/ufl-F2BJRXME.js +16 -0
- 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/usfl-4CR5RMZJ.js +32 -0
- package/dist/usfl-LD7HP2YK.js +14 -0
- package/dist/usfl-VU4YSJQD.js +19 -0
- package/dist/ushl-CPG2LB4L.js +73 -0
- package/dist/ushl-PQL6HJAD.js +88 -0
- package/dist/ushl-ZT2ATPHK.js +22 -0
- package/dist/vega.d.ts +85 -0
- package/dist/vega.js +362 -0
- package/dist/vega.js.map +1 -0
- package/dist/victory.d.ts +107 -0
- package/dist/victory.js +38 -0
- package/dist/victory.js.map +1 -0
- package/dist/wbb-3KHCDYTK.js +5087 -0
- package/dist/wbb-BMX53Q3Q.js +369 -0
- package/dist/wbb-QNRN2CJJ.js +3620 -0
- package/dist/whl-4ATYZWUB.js +97 -0
- package/dist/whl-RVFPL6MH.js +79 -0
- package/dist/whl-Y7ASKYMM.js +28 -0
- package/dist/wnba-FWX5SFVI.js +232 -0
- package/dist/wnba-GX6HZR4B.js +220 -0
- package/dist/wnba-NTMWIPP6.js +20 -0
- package/dist/xfl-43R2JMJK.js +30 -0
- package/dist/xfl-H3BXQPZZ.js +16 -0
- package/dist/xfl-XON7A7FI.js +69 -0
- package/package.json +366 -3
|
@@ -0,0 +1,300 @@
|
|
|
1
|
+
import { contentId } from './chunk-WQT4IVLX.js';
|
|
2
|
+
import { binner } from './chunk-C2CDY5OG.js';
|
|
3
|
+
import { InputError } from './chunk-E7G7VSLV.js';
|
|
4
|
+
import { BASKETBALL_ZONES, basketballZoneOf } from '@sportsdataverse/sporty';
|
|
5
|
+
|
|
6
|
+
var latticeOf = (cell) => binner(typeof cell === "number" ? { radius: cell } : cell);
|
|
7
|
+
var made = (s) => s.shot_result === "Made";
|
|
8
|
+
var split = (attempts, makes) => ({
|
|
9
|
+
attempts,
|
|
10
|
+
makes,
|
|
11
|
+
fgPct: attempts > 0 ? makes / attempts : null
|
|
12
|
+
});
|
|
13
|
+
var shotZone = (s) => basketballZoneOf(s.x_legacy, s.y_legacy, s.shot_value, { scale: 10 });
|
|
14
|
+
var onCourt = (s) => Math.abs(s.x_legacy) <= 250 && s.y_legacy >= -52.5 && Number.isFinite(s.y_legacy);
|
|
15
|
+
function binShots(shots, cell) {
|
|
16
|
+
const bins = latticeOf(cell).bins(shots.filter(onCourt), {
|
|
17
|
+
x: (s) => s.x_legacy,
|
|
18
|
+
y: (s) => s.y_legacy
|
|
19
|
+
});
|
|
20
|
+
return bins.map((b) => {
|
|
21
|
+
let distance = 0;
|
|
22
|
+
let makes = 0;
|
|
23
|
+
const zones = /* @__PURE__ */ new Map();
|
|
24
|
+
for (const s of b) {
|
|
25
|
+
distance += s.shot_distance;
|
|
26
|
+
if (made(s)) makes += 1;
|
|
27
|
+
const z = shotZone(s);
|
|
28
|
+
zones.set(z, (zones.get(z) ?? 0) + 1);
|
|
29
|
+
}
|
|
30
|
+
let zone = "mid_range";
|
|
31
|
+
let most = -1;
|
|
32
|
+
for (const [z, n] of zones) {
|
|
33
|
+
if (n > most) {
|
|
34
|
+
zone = z;
|
|
35
|
+
most = n;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
return { x: b.x, y: b.y, ...split(b.length, makes), meanDistance: distance / b.length, zone };
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
var LEAGUE_PRIOR_ATTEMPTS = 25;
|
|
42
|
+
function shrunkDiff(makes, attempts, league, k = LEAGUE_PRIOR_ATTEMPTS) {
|
|
43
|
+
return (makes + k * league) / (attempts + k) - league;
|
|
44
|
+
}
|
|
45
|
+
function leagueIndex(league, cell) {
|
|
46
|
+
const { lattice } = latticeOf(cell);
|
|
47
|
+
return {
|
|
48
|
+
...lattice,
|
|
49
|
+
cells: binShots(league, lattice).map(({ x, y, attempts, fgPct }) => ({ x, y, attempts, fgPct })),
|
|
50
|
+
zones: statsByZone(league)
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
var centres = /* @__PURE__ */ new WeakMap();
|
|
54
|
+
function cellsVsLeague(player, league, minLeague = LEAGUE_PRIOR_ATTEMPTS) {
|
|
55
|
+
let byCentre = centres.get(league);
|
|
56
|
+
if (byCentre === void 0) {
|
|
57
|
+
byCentre = new Map(league.cells.map((h) => [`${h.x},${h.y}`, h]));
|
|
58
|
+
centres.set(league, byCentre);
|
|
59
|
+
}
|
|
60
|
+
const index = byCentre;
|
|
61
|
+
return binShots(player, league).map((h) => {
|
|
62
|
+
const l = index.get(`${h.x},${h.y}`);
|
|
63
|
+
return { ...h, leagueFgPct: l && l.attempts >= minLeague ? l.fgPct : league.zones[h.zone].fgPct };
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
function quantile(values, p) {
|
|
67
|
+
const v = values.filter((x) => x != null && !Number.isNaN(x)).sort((a, b) => a - b);
|
|
68
|
+
const n = v.length;
|
|
69
|
+
if (n === 0) return void 0;
|
|
70
|
+
if (n < 2) return v[0];
|
|
71
|
+
const i = (n - 1) * p;
|
|
72
|
+
const i0 = Math.floor(i);
|
|
73
|
+
const v0 = v[i0];
|
|
74
|
+
const v1 = v[i0 + 1];
|
|
75
|
+
return v0 + (v1 - v0) * (i - i0);
|
|
76
|
+
}
|
|
77
|
+
function sizeCells(cells, o) {
|
|
78
|
+
const R2 = binner(o).size;
|
|
79
|
+
let cap;
|
|
80
|
+
let size;
|
|
81
|
+
if ((o.rule ?? "sqrt-p95") === "sqrt-p95") {
|
|
82
|
+
cap = Math.max(
|
|
83
|
+
2,
|
|
84
|
+
Math.ceil(
|
|
85
|
+
quantile(
|
|
86
|
+
cells.map((h) => h.attempts),
|
|
87
|
+
0.95
|
|
88
|
+
) ?? 2
|
|
89
|
+
)
|
|
90
|
+
);
|
|
91
|
+
const top = Math.sqrt(cap);
|
|
92
|
+
size = (a) => R2 * Math.min(1, Math.max(0, Math.sqrt(Math.max(0, a)) / top));
|
|
93
|
+
} else {
|
|
94
|
+
cap = R2;
|
|
95
|
+
size = (a) => a > R2 ? R2 : a < 2 ? 0 : a;
|
|
96
|
+
}
|
|
97
|
+
return {
|
|
98
|
+
r: cells.map((h) => size(h.attempts)),
|
|
99
|
+
cap,
|
|
100
|
+
size,
|
|
101
|
+
steps: [1, Math.max(2, Math.round(cap / 2)), cap]
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
function statsByZone(shots) {
|
|
105
|
+
const acc = new Map(BASKETBALL_ZONES.map((z) => [z, [0, 0]]));
|
|
106
|
+
for (const s of shots) {
|
|
107
|
+
const z = acc.get(shotZone(s));
|
|
108
|
+
if (z === void 0) continue;
|
|
109
|
+
z[0] += 1;
|
|
110
|
+
if (made(s)) z[1] += 1;
|
|
111
|
+
}
|
|
112
|
+
return Object.fromEntries(
|
|
113
|
+
BASKETBALL_ZONES.map((z) => {
|
|
114
|
+
const [a, m] = acc.get(z) ?? [0, 0];
|
|
115
|
+
return [z, split(a, m)];
|
|
116
|
+
})
|
|
117
|
+
);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// src/shots/diff.ts
|
|
121
|
+
var hex = (s) => [
|
|
122
|
+
Number.parseInt(s.slice(0, 2), 16),
|
|
123
|
+
Number.parseInt(s.slice(2, 4), 16),
|
|
124
|
+
Number.parseInt(s.slice(4, 6), 16)
|
|
125
|
+
];
|
|
126
|
+
var RDBU = ("67001fb2182bd6604df4a582fddbc7f7f7f7d1e5f092c5de4393c32166ac053061".match(/.{6}/g) ?? []).map(hex);
|
|
127
|
+
var MASTER = ["8d0801", "bf0603", "f4d58d", "708d81", "195943"].map(hex);
|
|
128
|
+
var MASTER_DOMAIN = [-0.99, -0.15, 0, 0.15, 0.99];
|
|
129
|
+
var DARK_NEUTRAL = [48, 48, 48];
|
|
130
|
+
var clampi = (v) => Math.max(0, Math.min(255, Math.round(v) || 0));
|
|
131
|
+
var fmt = (c) => `rgb(${clampi(c[0])}, ${clampi(c[1])}, ${clampi(c[2])})`;
|
|
132
|
+
function basis(t1, v0, v1, v2, v3) {
|
|
133
|
+
const t2 = t1 * t1;
|
|
134
|
+
const t3 = t2 * t1;
|
|
135
|
+
return ((1 - 3 * t1 + 3 * t2 - t3) * v0 + (4 - 6 * t2 + 3 * t3) * v1 + (1 + 3 * t1 + 3 * t2 - 3 * t3) * v2 + t3 * v3) / 6;
|
|
136
|
+
}
|
|
137
|
+
function spline(values, tIn) {
|
|
138
|
+
const n = values.length - 1;
|
|
139
|
+
let t = tIn;
|
|
140
|
+
let i;
|
|
141
|
+
if (t <= 0) {
|
|
142
|
+
t = 0;
|
|
143
|
+
i = 0;
|
|
144
|
+
} else if (t >= 1) {
|
|
145
|
+
t = 1;
|
|
146
|
+
i = n - 1;
|
|
147
|
+
} else i = Math.floor(t * n);
|
|
148
|
+
const v1 = values[i];
|
|
149
|
+
const v2 = values[i + 1];
|
|
150
|
+
const v0 = i > 0 ? values[i - 1] : 2 * v1 - v2;
|
|
151
|
+
const v3 = i < n - 1 ? values[i + 2] : 2 * v2 - v1;
|
|
152
|
+
return basis((t - i / n) * n, v0, v1, v2, v3);
|
|
153
|
+
}
|
|
154
|
+
var channel = (k) => RDBU.map((c) => c[k]);
|
|
155
|
+
var R = channel(0);
|
|
156
|
+
var G = channel(1);
|
|
157
|
+
var B = channel(2);
|
|
158
|
+
var rdbu = (t) => [spline(R, t), spline(G, t), spline(B, t)];
|
|
159
|
+
var lerp = (a, b, t) => [
|
|
160
|
+
a[0] + t * (b[0] - a[0]),
|
|
161
|
+
a[1] + t * (b[1] - a[1]),
|
|
162
|
+
a[2] + t * (b[2] - a[2])
|
|
163
|
+
];
|
|
164
|
+
function piecewise(domain, range, x) {
|
|
165
|
+
let i = 1;
|
|
166
|
+
while (i < domain.length - 1 && domain[i] <= x) i++;
|
|
167
|
+
const a = domain[i - 1];
|
|
168
|
+
const b = domain[i];
|
|
169
|
+
return lerp(range[i - 1], range[i], (x - a) / (b - a));
|
|
170
|
+
}
|
|
171
|
+
function diffScale(o = {}) {
|
|
172
|
+
const palette = o.palette ?? "rdbu";
|
|
173
|
+
const D = o.domain ?? 0.15;
|
|
174
|
+
if (!(Number.isFinite(D) && D > 0))
|
|
175
|
+
throw new InputError(`diffScale domain must be a finite number > 0, got ${String(D)}`);
|
|
176
|
+
const nullColor = o.nullColor ?? "var(--sdv-muted, #525252)";
|
|
177
|
+
let colour;
|
|
178
|
+
let plot;
|
|
179
|
+
let stops = [0, 0.25, 0.5, 0.75, 1].map((t) => (t * 2 - 1) * D);
|
|
180
|
+
if (palette === "master") {
|
|
181
|
+
const range = MASTER.map(fmt);
|
|
182
|
+
colour = (d) => Number.isFinite(d) ? fmt(piecewise(MASTER_DOMAIN, MASTER, d)) : range[d > 0 ? range.length - 1 : 0];
|
|
183
|
+
plot = { type: "linear", domain: MASTER_DOMAIN, range, interpolate: "rgb", clamp: false };
|
|
184
|
+
stops = MASTER_DOMAIN;
|
|
185
|
+
} else if (o.theme === "dark") {
|
|
186
|
+
const ends = [rdbu(0.85), DARK_NEUTRAL, rdbu(0.15)].map(
|
|
187
|
+
(c) => [clampi(c[0]), clampi(c[1]), clampi(c[2])]
|
|
188
|
+
);
|
|
189
|
+
const domain = [-D, 0, D];
|
|
190
|
+
colour = (d) => fmt(piecewise(domain, ends, Math.max(-D, Math.min(D, d))));
|
|
191
|
+
plot = { type: "linear", domain, range: ends.map(fmt), interpolate: "rgb", clamp: true };
|
|
192
|
+
} else {
|
|
193
|
+
const k = 0.5 / D;
|
|
194
|
+
colour = (d) => fmt(rdbu(1 - Math.max(0, Math.min(1, 0.5 + d * k))));
|
|
195
|
+
plot = { type: "diverging", scheme: "RdBu", reverse: true, domain: [-D, D], pivot: 0, clamp: true };
|
|
196
|
+
}
|
|
197
|
+
const scale = (diff) => diff === null || Number.isNaN(diff) ? nullColor : colour(diff);
|
|
198
|
+
return Object.assign(scale, { plot, stops });
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// src/shots/signature.ts
|
|
202
|
+
function kernelSmooth(values, weights, sigma) {
|
|
203
|
+
if (sigma === 0) return [...values];
|
|
204
|
+
const reach = Math.ceil(sigma * 3);
|
|
205
|
+
return values.map((_, i) => {
|
|
206
|
+
let num = 0;
|
|
207
|
+
let den = 0;
|
|
208
|
+
for (let j = Math.max(0, i - reach); j <= Math.min(values.length - 1, i + reach); j++) {
|
|
209
|
+
const k = Math.exp(-((i - j) ** 2) / (2 * sigma * sigma)) * (weights ? weights[j] ?? 0 : 1);
|
|
210
|
+
num += k * (values[j] ?? 0);
|
|
211
|
+
den += k;
|
|
212
|
+
}
|
|
213
|
+
return den > 0 ? num / den : 0;
|
|
214
|
+
});
|
|
215
|
+
}
|
|
216
|
+
function kernelSum(values, sigma) {
|
|
217
|
+
if (sigma === 0) return [...values];
|
|
218
|
+
const reach = Math.ceil(sigma * 3);
|
|
219
|
+
return values.map((_, i) => {
|
|
220
|
+
let sum = 0;
|
|
221
|
+
for (let j = Math.max(0, i - reach); j <= Math.min(values.length - 1, i + reach); j++) {
|
|
222
|
+
sum += Math.exp(-((i - j) ** 2) / (2 * sigma * sigma)) * (values[j] ?? 0);
|
|
223
|
+
}
|
|
224
|
+
return sum;
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
function signaturePoints(bins, o = {}) {
|
|
228
|
+
const step = o.step ?? 0.25;
|
|
229
|
+
if (!(step > 0)) throw new InputError(`signaturePoints step must be > 0, got ${String(step)}`);
|
|
230
|
+
const smooth = o.smooth ?? true;
|
|
231
|
+
const min = o.minAttempts ?? 5;
|
|
232
|
+
const prior = o.prior ?? LEAGUE_PRIOR_ATTEMPTS;
|
|
233
|
+
const attempts = kernelSum(
|
|
234
|
+
bins.map((b) => b.attempts),
|
|
235
|
+
smooth ? 0.9 : 0
|
|
236
|
+
);
|
|
237
|
+
const makes = kernelSum(
|
|
238
|
+
bins.map((b) => b.makes),
|
|
239
|
+
smooth ? 0.9 : 0
|
|
240
|
+
);
|
|
241
|
+
const share = kernelSmooth(
|
|
242
|
+
bins.map((b) => b.share),
|
|
243
|
+
null,
|
|
244
|
+
smooth ? 1 : 0
|
|
245
|
+
);
|
|
246
|
+
const league = kernelSmooth(
|
|
247
|
+
bins.map((b) => b.leagueFgPct ?? 0),
|
|
248
|
+
bins.map((b) => b.leagueFgPct === null ? 0 : 1),
|
|
249
|
+
smooth ? 0.6 : 0
|
|
250
|
+
);
|
|
251
|
+
let end = null;
|
|
252
|
+
for (let i = bins.length - 1; i >= 0; i--) {
|
|
253
|
+
const b = bins[i];
|
|
254
|
+
if (b !== void 0 && b.attempts >= min) {
|
|
255
|
+
end = b.distance;
|
|
256
|
+
break;
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
const last = bins.length - 1;
|
|
260
|
+
const lerp2 = (a, d) => {
|
|
261
|
+
const i = Math.min(Math.floor(d), last);
|
|
262
|
+
const j = Math.min(i + 1, last);
|
|
263
|
+
const ai = a[i] ?? 0;
|
|
264
|
+
return ai + ((a[j] ?? 0) - ai) * (d - i);
|
|
265
|
+
};
|
|
266
|
+
return Array.from({ length: Math.round(last / step) + 1 }, (_, k) => {
|
|
267
|
+
const d = k * step;
|
|
268
|
+
const a = lerp2(attempts, d);
|
|
269
|
+
const m = lerp2(makes, d);
|
|
270
|
+
const hasLeague = bins[Math.min(Math.round(d), last)]?.leagueFgPct != null;
|
|
271
|
+
const leagueFgPct = hasLeague ? lerp2(league, d) : null;
|
|
272
|
+
const ok = end !== null && d <= end && a >= min;
|
|
273
|
+
return {
|
|
274
|
+
distance: d,
|
|
275
|
+
fgPct: ok ? m / a : null,
|
|
276
|
+
leagueFgPct,
|
|
277
|
+
share: lerp2(share, d),
|
|
278
|
+
colourDiff: ok && leagueFgPct !== null ? shrunkDiff(m, a, leagueFgPct, prior) : null
|
|
279
|
+
};
|
|
280
|
+
});
|
|
281
|
+
}
|
|
282
|
+
var SIGNATURE_NAME = "shooting signature: FG% by distance against the league";
|
|
283
|
+
function signatureGradient(points, fill) {
|
|
284
|
+
const drawn = points.filter((p) => p.fgPct !== null);
|
|
285
|
+
const maxFt = points.at(-1)?.distance ?? 0;
|
|
286
|
+
const step = (points[1]?.distance ?? 0) - (points[0]?.distance ?? 0);
|
|
287
|
+
const every = step > 0 ? Math.max(1, Math.round(0.5 / step)) : 1;
|
|
288
|
+
const stops = drawn.filter((_, i) => i % every === 0).map((p) => [maxFt ? p.distance / maxFt * 100 : 0, fill(p.colourDiff)]);
|
|
289
|
+
const text = stops.map((s) => s.join(" ")).join(";");
|
|
290
|
+
return {
|
|
291
|
+
maxShare: Math.max(0, ...drawn.map((p) => p.share)) || 1,
|
|
292
|
+
maxFt,
|
|
293
|
+
stops,
|
|
294
|
+
id: (x1, x2) => contentId("sdv-signature", `${x1} ${x2}|${text}`)
|
|
295
|
+
};
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
export { LEAGUE_PRIOR_ATTEMPTS, SIGNATURE_NAME, binShots, cellsVsLeague, diffScale, leagueIndex, made, shrunkDiff, signatureGradient, signaturePoints, sizeCells, split, statsByZone };
|
|
299
|
+
//# sourceMappingURL=chunk-H2C3FMMA.js.map
|
|
300
|
+
//# sourceMappingURL=chunk-H2C3FMMA.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/shots/aggregate.ts","../src/shots/diff.ts","../src/shots/signature.ts"],"names":["R","lerp"],"mappings":"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","file":"chunk-H2C3FMMA.js","sourcesContent":["// Cell aggregations, ported from blazing-the-nets `main` lib/data/aggregate.ts (@31427b8). Pure; the parity gate\n// is fixtures/shots/oracle.json (hexagons). Squares (J38) run the same code on `squarebin`'s lattice.\nimport { BASKETBALL_ZONES, type BasketballZone, basketballZoneOf } from \"@sportsdataverse/sporty\";\nimport { type Binner, type BinnerOptions, type Lattice, binner } from \"../bins/index.js\";\n\n/** A number is a hexagon radius (the parity default); an object is any lattice, squares included (J38). */\nconst latticeOf = (cell: number | BinnerOptions): Binner =>\n binner(typeof cell === \"number\" ? { radius: cell } : cell);\n\n/**\n * One field-goal attempt, as the `nba_stats_shots` release ships it: `x_legacy`/`y_legacy` in tenths of a foot,\n * hoop at the origin, y toward half court; `shot_distance` in ROUNDED feet. Map other schemas once, first.\n */\nexport interface ShotRow {\n readonly x_legacy: number;\n readonly y_legacy: number;\n readonly shot_distance: number;\n /** 2 or 3; zones read it (the scorer's call, not the coordinates). */\n readonly shot_value: number;\n /** `\"Made\"` or `\"Missed\"`. */\n readonly shot_result: string;\n}\n\n/** Attempts, makes and FG% (`makes / attempts`, null with no attempts): the counts every shot aggregate carries. */\nexport interface Split {\n readonly attempts: number;\n readonly makes: number;\n readonly fgPct: number | null;\n}\n\n/** @internal */\nexport const made = (s: ShotRow): boolean => s.shot_result === \"Made\"; // aggregate.ts:14\n/** @internal */\nexport const split = (attempts: number, makes: number): Split => ({\n attempts,\n makes,\n fgPct: attempts > 0 ? makes / attempts : null,\n}); // aggregate.ts:16-18\n\n/** The shot's zone (sporty, legacy tenths). */\nexport const shotZone = (s: ShotRow): BasketballZone =>\n basketballZoneOf(s.x_legacy, s.y_legacy, s.shot_value, { scale: 10 });\n\n/**\n * Inside the sidelines and in front of the baseline (aggregate.ts:81-84); releases carry a few impossible points.\n * Also finite: `main` lets `y_legacy = Infinity` through. x's bound already rejects NaN and ±Infinity.\n */\nconst onCourt = (s: ShotRow): boolean =>\n Math.abs(s.x_legacy) <= 250 && s.y_legacy >= -52.5 && Number.isFinite(s.y_legacy);\n\n/** One cell of `binShots`: its centre, counts, mean shot distance and majority zone. */\nexport interface CellBin extends Split {\n /** Cell centre, legacy tenths. */\n readonly x: number;\n readonly y: number;\n readonly meanDistance: number;\n /** The zone most of the cell's attempts fall in (first seen wins a tie). */\n readonly zone: BasketballZone;\n}\n\n/**\n * Bin shots in DATA space (legacy tenths), dropping off-court points (aggregate.ts:86-104) and points with a\n * non-finite coordinate (`main` lets `y_legacy = Infinity` through). `cell` is a hexagon\n * radius (10 = a 1 ft hex) or any `binner` lattice, e.g. `{ shape: \"square\", side: 15 }`. Bins come out in\n * first-seen order (d3-hexbin's, for hexagons).\n *\n * @example\n * ```ts\n * import { binShots } from \"@sportsdataverse/sdvplot/shots\";\n *\n * // Brooklyn shots from the sportsdataverse-data nba_stats_shots release, 2025-26\n * const shots = [\n * { x_legacy: -1, y_legacy: 7, shot_distance: 1, shot_value: 2, shot_result: \"Made\" },\n * { x_legacy: 0, y_legacy: 0, shot_distance: 0, shot_value: 2, shot_result: \"Missed\" },\n * { x_legacy: -44, y_legacy: 252, shot_distance: 26, shot_value: 3, shot_result: \"Made\" },\n * ];\n * binShots(shots, 15);\n * binShots(shots, { shape: \"square\", side: 15 });\n * ```\n */\nexport function binShots(shots: readonly ShotRow[], cell: number | BinnerOptions): CellBin[] {\n const bins = latticeOf(cell).bins(shots.filter(onCourt), {\n x: (s) => s.x_legacy,\n y: (s) => s.y_legacy,\n });\n return bins.map((b) => {\n let distance = 0;\n let makes = 0;\n const zones = new Map<BasketballZone, number>();\n for (const s of b) {\n distance += s.shot_distance;\n if (made(s)) makes += 1;\n const z = shotZone(s);\n zones.set(z, (zones.get(z) ?? 0) + 1);\n }\n let zone: BasketballZone = \"mid_range\";\n let most = -1;\n for (const [z, n] of zones) {\n if (n > most) {\n zone = z;\n most = n;\n }\n }\n return { x: b.x, y: b.y, ...split(b.length, makes), meanDistance: distance / b.length, zone };\n });\n}\n\n/**\n * League attempts behind a cell's league rate before it falls back to the zone, and the colour prior: one number for\n * both (aggregate.ts:106-110). It is blazing-the-nets' tunable, set in its commit 25c3ab9f (\"shrunk colours\"), not a\n * derived or measured constant; `cellsVsLeague`'s `minLeague`, `shrunkDiff`'s `k` and `signaturePoints`' `prior`\n * override it per call.\n */\nexport const LEAGUE_PRIOR_ATTEMPTS = 25;\n\n/**\n * FG% minus the league rate `league`, shrunk toward it by a `k`-attempt prior: `(makes + k*L)/(attempts + k) - L`\n * (aggregate.ts:112-119). `k = 0` is the raw difference (blazing-the-nets `master`). For colour, not labels.\n *\n * @example\n * ```ts\n * import { LEAGUE_PRIOR_ATTEMPTS, shrunkDiff } from \"@sportsdataverse/sdvplot/shots\";\n *\n * // Brooklyn 2025-26, the radius-15 hexagon centred (77.9, 45): 4 of 4, where the league made 270 of 636\n * shrunkDiff(4, 4, 270 / 636, LEAGUE_PRIOR_ATTEMPTS); // +0.08, not the raw +0.58\n * ```\n */\nexport function shrunkDiff(\n makes: number,\n attempts: number,\n league: number,\n k: number = LEAGUE_PRIOR_ATTEMPTS,\n): number {\n return (makes + k * league) / (attempts + k) - league;\n}\n\n/** One league cell in a `LeagueIndex`: centre (legacy tenths), attempts and FG%. */\nexport interface LeagueCell {\n readonly x: number;\n readonly y: number;\n readonly attempts: number;\n readonly fgPct: number | null;\n}\n/**\n * A season's league cells and zone rates on one lattice (`{ radius }` for hexagons, `{ shape: \"square\", side }`):\n * plain JSON, computed once (aggregate.ts:125-146). blazing-the-nets names the cells `hexes`; here they are `cells`\n * for either shape, so map that key when loading its JSON.\n */\nexport type LeagueIndex = Lattice & {\n readonly cells: readonly LeagueCell[];\n readonly zones: Readonly<Record<BasketballZone, Split>>;\n};\n/** A player cell with the league FG% it is compared to (`cellsVsLeague`, `cellsVsDistance`); null with no league figure. */\nexport interface CellVsLeague extends CellBin {\n readonly leagueFgPct: number | null;\n}\n\n/**\n * The league index every player chart compares against (aggregate.ts:140-146), on a hexagon radius or any `binner`\n * lattice; it stores the resolved lattice, so `equalArea` becomes the square's `side`.\n *\n * @example\n * ```ts\n * import { leagueIndex } from \"@sportsdataverse/sdvplot/shots\";\n *\n * // Brooklyn shots from the sportsdataverse-data nba_stats_shots release, 2025-26\n * const shots = [\n * { x_legacy: -1, y_legacy: 7, shot_distance: 1, shot_value: 2, shot_result: \"Made\" },\n * { x_legacy: 0, y_legacy: 0, shot_distance: 0, shot_value: 2, shot_result: \"Missed\" },\n * { x_legacy: -44, y_legacy: 252, shot_distance: 26, shot_value: 3, shot_result: \"Made\" },\n * ];\n * leagueIndex(shots, 15).cells;\n * ```\n */\nexport function leagueIndex(league: readonly ShotRow[], cell: number | BinnerOptions): LeagueIndex {\n const { lattice } = latticeOf(cell);\n return {\n ...lattice,\n cells: binShots(league, lattice).map(({ x, y, attempts, fgPct }) => ({ x, y, attempts, fgPct })),\n zones: statsByZone(league),\n };\n}\n\nconst centres = new WeakMap<LeagueIndex, Map<string, LeagueCell>>();\n/**\n * Player cells with the league FG% of the SAME cell (the player is binned on the index's own lattice, so the\n * centres are equal); a cell the league shot fewer than `minLeague` times from falls back to its zone's league rate\n * (aggregate.ts:155-165; blazing-the-nets `main`).\n *\n * @example\n * ```ts\n * import { cellsVsLeague, leagueIndex } from \"@sportsdataverse/sdvplot/shots\";\n *\n * // Brooklyn shots from the sportsdataverse-data nba_stats_shots release, 2025-26\n * const made = { x_legacy: 0, y_legacy: 0, shot_distance: 0, shot_value: 2, shot_result: \"Made\" };\n * const missed = { ...made, shot_result: \"Missed\" };\n * cellsVsLeague([made], leagueIndex([made, missed], 15));\n * ```\n */\nexport function cellsVsLeague(\n player: readonly ShotRow[],\n league: LeagueIndex,\n minLeague: number = LEAGUE_PRIOR_ATTEMPTS,\n): CellVsLeague[] {\n let byCentre = centres.get(league);\n if (byCentre === undefined) {\n byCentre = new Map(league.cells.map((h) => [`${h.x},${h.y}`, h]));\n centres.set(league, byCentre);\n }\n const index = byCentre;\n return binShots(player, league).map((h) => {\n const l = index.get(`${h.x},${h.y}`);\n return { ...h, leagueFgPct: l && l.attempts >= minLeague ? l.fgPct : league.zones[h.zone].fgPct };\n });\n}\n\n/** How a cell's attempts set its size: `main` (sqrt to the 95th percentile) or `master` (linear, capped). */\nexport type SizeRule = \"sqrt-p95\" | \"linear-cap\";\n/** `sizeCells`' result: each cell's size, the cap, the size of any count, and the counts for a size key. */\nexport interface CellSizes {\n /** Size per cell in legacy tenths: a hexagon's circumradius, or a square's side. 0 hides a cell. */\n readonly r: readonly number[];\n /** Attempts at which size saturates. */\n readonly cap: number;\n /** The size of any attempt count, for a size key. */\n readonly size: (attempts: number) => number;\n /** A size key: 1, half the cap, the cap (blazing-the-nets `main` `lib/charts/hexShotChart.ts:199`). */\n readonly steps: readonly number[];\n}\n\n/** d3-array 3.2.4 `quantile` (src/quantile.js:10-20, R-7) over finite values; undefined when there are none. */\nfunction quantile(values: readonly number[], p: number): number | undefined {\n const v = values.filter((x) => x != null && !Number.isNaN(x)).sort((a, b) => a - b);\n const n = v.length;\n if (n === 0) return undefined;\n if (p <= 0 || n < 2) return v[0];\n if (p >= 1) return v[n - 1];\n const i = (n - 1) * p;\n const i0 = Math.floor(i);\n const v0 = v[i0] as number;\n const v1 = v[i0 + 1] as number;\n return v0 + (v1 - v0) * (i - i0);\n}\n\n/**\n * Frequency size for each cell (the dual encoding's size half; colour is `diffScale`), on the lattice's full cell\n * size `R` (a hexagon's radius, or a square's side; pass the index itself: `sizeCells(cells, index)`).\n * `\"sqrt-p95\"` (default, `main` `hexShotChart.ts:65-81`): a sqrt scale of attempts to `R`, capped at the 95th\n * percentile so one busy rim cell does not shrink the rest; area follows attempts for either shape.\n * `\"linear-cap\"` (`master` `Hexagon/index.js:22-30`): attempts in tenths, capped at `R`, cells under 2 attempts\n * hidden.\n *\n * @example\n * ```ts\n * import { binShots, sizeCells } from \"@sportsdataverse/sdvplot/shots\";\n *\n * // Brooklyn shots from the sportsdataverse-data nba_stats_shots release, 2025-26\n * const shots = [\n * { x_legacy: -1, y_legacy: 7, shot_distance: 1, shot_value: 2, shot_result: \"Made\" },\n * { x_legacy: 0, y_legacy: 0, shot_distance: 0, shot_value: 2, shot_result: \"Missed\" },\n * { x_legacy: -44, y_legacy: 252, shot_distance: 26, shot_value: 3, shot_result: \"Made\" },\n * ];\n * sizeCells(binShots(shots, 15), { radius: 15 }).r; // the 2-attempt rim cell is full size, the 1-attempt three smaller\n * ```\n */\nexport function sizeCells(\n cells: readonly { readonly attempts: number }[],\n o: BinnerOptions & { readonly rule?: SizeRule },\n): CellSizes {\n const R = binner(o).size; // throws InputError on a size that is not a finite number > 0\n let cap: number;\n let size: (a: number) => number;\n if ((o.rule ?? \"sqrt-p95\") === \"sqrt-p95\") {\n cap = Math.max(\n 2,\n Math.ceil(\n quantile(\n cells.map((h) => h.attempts),\n 0.95,\n ) ?? 2,\n ),\n );\n const top = Math.sqrt(cap);\n size = (a) => R * Math.min(1, Math.max(0, Math.sqrt(Math.max(0, a)) / top));\n } else {\n cap = R;\n size = (a) => (a > R ? R : a < 2 ? 0 : a);\n }\n return {\n r: cells.map((h) => size(h.attempts)),\n cap,\n size,\n steps: [1, Math.max(2, Math.round(cap / 2)), cap],\n };\n}\n\n/**\n * Attempts, makes and FG% per zone (aggregate.ts:269-277).\n *\n * @example\n * ```ts\n * import { statsByZone } from \"@sportsdataverse/sdvplot/shots\";\n *\n * // a Brooklyn corner three, 2025-26 (sportsdataverse-data nba_stats_shots)\n * statsByZone([{ x_legacy: 237, y_legacy: 34, shot_distance: 24, shot_value: 3, shot_result: \"Made\" }]);\n * ```\n */\nexport function statsByZone(shots: readonly ShotRow[]): Record<BasketballZone, Split> {\n const acc = new Map<BasketballZone, [number, number]>(BASKETBALL_ZONES.map((z) => [z, [0, 0]]));\n for (const s of shots) {\n const z = acc.get(shotZone(s));\n if (z === undefined) continue;\n z[0] += 1;\n if (made(s)) z[1] += 1;\n }\n return Object.fromEntries(\n BASKETBALL_ZONES.map((z) => {\n const [a, m] = acc.get(z) ?? [0, 0];\n return [z, split(a, m)];\n }),\n ) as Record<BasketballZone, Split>;\n}\n","// diffScale reproduces blazing-the-nets' diff colours with no d3 at runtime. \"rdbu\" = main lib/charts/theme.ts:47-64;\n// \"master\" = master src/components/HexShotchart/index.js:57-64. The RdBu ramp ports d3-scale-chromatic 3.1.0\n// src/diverging/RdBu.js:13 + src/ramp.js:3 and d3-interpolate 3.0.1 src/basis.js:1-19 + src/rgb.js:28-52 (ISC,\n// Mike Bostock; notice in NOTICE.md). Strings match d3-color's `rgb(r, g, b)` exactly.\nimport { InputError } from \"../errors.js\";\n\ntype Rgb = readonly [number, number, number];\nconst hex = (s: string): Rgb => [\n Number.parseInt(s.slice(0, 2), 16),\n Number.parseInt(s.slice(2, 4), 16),\n Number.parseInt(s.slice(4, 6), 16),\n];\nconst RDBU: readonly Rgb[] = (\n \"67001fb2182bd6604df4a582fddbc7f7f7f7d1e5f092c5de4393c32166ac053061\".match(/.{6}/g) ?? []\n).map(hex);\nconst MASTER: readonly Rgb[] = [\"8d0801\", \"bf0603\", \"f4d58d\", \"708d81\", \"195943\"].map(hex);\nconst MASTER_DOMAIN = [-0.99, -0.15, 0, 0.15, 0.99] as const;\nconst DARK_NEUTRAL: Rgb = [0x30, 0x30, 0x30]; // theme.ts:54\n\n/** d3-color's formatter: each channel rounded and clamped to 0-255. */\nconst clampi = (v: number): number => Math.max(0, Math.min(255, Math.round(v) || 0));\nconst fmt = (c: Rgb): string => `rgb(${clampi(c[0])}, ${clampi(c[1])}, ${clampi(c[2])})`;\n\nfunction basis(t1: number, v0: number, v1: number, v2: number, v3: number): number {\n const t2 = t1 * t1;\n const t3 = t2 * t1;\n return (\n ((1 - 3 * t1 + 3 * t2 - t3) * v0 +\n (4 - 6 * t2 + 3 * t3) * v1 +\n (1 + 3 * t1 + 3 * t2 - 3 * t3) * v2 +\n t3 * v3) /\n 6\n );\n}\nfunction spline(values: readonly number[], tIn: number): number {\n const n = values.length - 1;\n let t = tIn;\n let i: number;\n if (t <= 0) {\n t = 0;\n i = 0;\n } else if (t >= 1) {\n t = 1;\n i = n - 1;\n } else i = Math.floor(t * n);\n const v1 = values[i] as number;\n const v2 = values[i + 1] as number;\n const v0 = i > 0 ? (values[i - 1] as number) : 2 * v1 - v2;\n const v3 = i < n - 1 ? (values[i + 2] as number) : 2 * v2 - v1;\n return basis((t - i / n) * n, v0, v1, v2, v3);\n}\nconst channel = (k: 0 | 1 | 2): number[] => RDBU.map((c) => c[k]);\nconst R = channel(0);\nconst G = channel(1);\nconst B = channel(2);\n/** d3.interpolateRdBu(t), unrounded. */\nconst rdbu = (t: number): Rgb => [spline(R, t), spline(G, t), spline(B, t)];\n/** d3.interpolateRgb(a, b)(t). */\nconst lerp = (a: Rgb, b: Rgb, t: number): Rgb => [\n a[0] + t * (b[0] - a[0]),\n a[1] + t * (b[1] - a[1]),\n a[2] + t * (b[2] - a[2]),\n];\n/** A d3 piecewise scaleLinear over colours: bisectRight segment, unclamped t unless the input was clamped. */\nfunction piecewise(domain: readonly number[], range: readonly Rgb[], x: number): Rgb {\n let i = 1;\n while (i < domain.length - 1 && (domain[i] as number) <= x) i++;\n const a = domain[i - 1] as number;\n const b = domain[i] as number;\n return lerp(range[i - 1] as Rgb, range[i] as Rgb, (x - a) / (b - a));\n}\n\n/** The diff colours: `\"rdbu\"` (blazing-the-nets `main`, the default) or `\"master\"` (its 2021 five stops). */\nexport type DiffPalette = \"rdbu\" | \"master\";\n/** Options for `diffScale`. */\nexport interface DiffScaleOptions {\n /**\n * `\"rdbu\"` (default): RdBu reversed, above league red (blazing-the-nets `main`), clamped at `±domain`.\n * `\"master\"`: the 2021 five-stop red-yellow-green, UNCLAMPED like master's d3 `scaleLinear`: a diff past the end\n * stops (±0.99) extrapolates, each channel then clamped to 0-255. An infinite diff takes the end colour (d3 gives\n * `rgb(0, 0, 0)` for `Infinity` and `rgb(0, 255, 0)` for `-Infinity`).\n */\n palette?: DiffPalette;\n /** `\"dark\"` swaps RdBu's near-white centre for `#303030` so an average mark recedes (`theme.ts:52-58`). Ignored by `\"master\"`. */\n theme?: \"light\" | \"dark\";\n /** Saturation in FG% fraction, finite and above 0 (else InputError); default 0.15 (`theme.ts:29`). `\"master\"` keeps its own stops. */\n domain?: number;\n /** Colour for `null` (no league figure); default `var(--sdv-muted, #525252)`. */\n nullColor?: string;\n}\n/** A diff colour function plus the equivalent Plot colour-scale options (for fills and `Plot.legend`). */\nexport interface DiffScale {\n (diff: number | null): string;\n /** Diffs to place gradient stops at: 5 across [-D, D] (`theme.ts:127-130`), or `master`'s own 5 stops. */\n readonly stops: readonly number[];\n readonly plot: {\n readonly type: \"diverging\" | \"linear\";\n readonly domain: readonly number[];\n readonly range?: readonly string[];\n readonly scheme?: \"RdBu\";\n readonly reverse?: boolean;\n readonly pivot?: number;\n readonly interpolate?: \"rgb\";\n readonly clamp: boolean;\n };\n}\n\n/**\n * The FG%-vs-league colour: `diffScale()(0.05)`. Light RdBu, dark RdBu, or `master`'s palette; `null` is\n * `nullColor`. `.plot` is the same scale as Observable Plot `color` options, so `Plot.legend({ color: s.plot })`\n * draws the matching key.\n *\n * @example\n * ```ts\n * import { diffScale } from \"@sportsdataverse/sdvplot/shots\";\n *\n * diffScale({ theme: \"dark\" })(0.05);\n * ```\n */\nexport function diffScale(o: DiffScaleOptions = {}): DiffScale {\n const palette = o.palette ?? \"rdbu\";\n const D = o.domain ?? 0.15;\n if (!(Number.isFinite(D) && D > 0))\n throw new InputError(`diffScale domain must be a finite number > 0, got ${String(D)}`);\n const nullColor = o.nullColor ?? \"var(--sdv-muted, #525252)\";\n let colour: (d: number) => string;\n let plot: DiffScale[\"plot\"];\n let stops: readonly number[] = [0, 0.25, 0.5, 0.75, 1].map((t) => (t * 2 - 1) * D);\n if (palette === \"master\") {\n const range = MASTER.map(fmt);\n // Deliberate divergence from d3: ±Infinity takes the end colour, where d3 gives black (+) or green (-).\n colour = (d) =>\n Number.isFinite(d)\n ? fmt(piecewise(MASTER_DOMAIN, MASTER, d))\n : (range[d > 0 ? range.length - 1 : 0] as string);\n plot = { type: \"linear\", domain: MASTER_DOMAIN, range, interpolate: \"rgb\", clamp: false };\n stops = MASTER_DOMAIN;\n } else if (o.theme === \"dark\") {\n // theme.ts:55-58: the ends are d3's ROUNDED rgb() strings, re-parsed, so round before interpolating.\n const ends: readonly Rgb[] = [rdbu(0.85), DARK_NEUTRAL, rdbu(0.15)].map(\n (c): Rgb => [clampi(c[0]), clampi(c[1]), clampi(c[2])],\n );\n const domain = [-D, 0, D];\n colour = (d) => fmt(piecewise(domain, ends, Math.max(-D, Math.min(D, d))));\n plot = { type: \"linear\", domain, range: ends.map(fmt), interpolate: \"rgb\", clamp: true };\n } else {\n // theme.ts:49-51: scaleDiverging(t => interpolateRdBu(1 - t)).domain([-D, 0, D]).clamp(true).\n const k = 0.5 / D;\n colour = (d) => fmt(rdbu(1 - Math.max(0, Math.min(1, 0.5 + d * k))));\n plot = { type: \"diverging\", scheme: \"RdBu\", reverse: true, domain: [-D, D], pivot: 0, clamp: true };\n }\n const scale = (diff: number | null): string =>\n diff === null || Number.isNaN(diff) ? nullColor : colour(diff);\n return Object.assign(scale, { plot, stops });\n}\n","// signaturePoints ports blazing-the-nets `main` lib/charts/shootingSignature.ts:13-100 (@31427b8).\nimport { contentId } from \"../content-id.js\";\nimport { InputError } from \"../errors.js\";\nimport { LEAGUE_PRIOR_ATTEMPTS, shrunkDiff } from \"./aggregate.js\";\nimport type { DistanceVsLeague } from \"./distance.js\";\n\n/** Gaussian kernel smoothing (`:27-39`); with `weights`, a weighted local mean. `sigma` 0 = no smoothing. */\nexport function kernelSmooth(\n values: readonly number[],\n weights: readonly number[] | null,\n sigma: number,\n): number[] {\n if (sigma === 0) return [...values];\n const reach = Math.ceil(sigma * 3);\n return values.map((_, i) => {\n let num = 0;\n let den = 0;\n for (let j = Math.max(0, i - reach); j <= Math.min(values.length - 1, i + reach); j++) {\n const k = Math.exp(-((i - j) ** 2) / (2 * sigma * sigma)) * (weights ? (weights[j] ?? 0) : 1);\n num += k * (values[j] ?? 0);\n den += k;\n }\n return den > 0 ? num / den : 0;\n });\n}\n\n/** Gaussian kernel sums, weight 1 at the bin itself (`:42-51`): effective makes or attempts near each bin. */\nexport function kernelSum(values: readonly number[], sigma: number): number[] {\n if (sigma === 0) return [...values];\n const reach = Math.ceil(sigma * 3);\n return values.map((_, i) => {\n let sum = 0;\n for (let j = Math.max(0, i - reach); j <= Math.min(values.length - 1, i + reach); j++) {\n sum += Math.exp(-((i - j) ** 2) / (2 * sigma * sigma)) * (values[j] ?? 0);\n }\n return sum;\n });\n}\n\n/** One shooting-signature sample (`signaturePoints`): the input of the Plot `shootingSignature` and d3 `appendSignature`. */\nexport interface SignaturePoint {\n readonly distance: number;\n /** Effective makes / attempts near this distance; null (a gap in the ribbon) under `minAttempts`. */\n readonly fgPct: number | null;\n readonly leagueFgPct: number | null;\n readonly share: number;\n /** The colour value: `shrunkDiff(makes, attempts, league, prior)`, null in a gap. */\n readonly colourDiff: number | null;\n}\n\n/** Options for `signaturePoints`; `master` mode is `{ step: 1, smooth: false, minAttempts: 1, prior: 0 }`. */\nexport interface SignatureOptions {\n /** Feet between samples; default 0.25 (`:16`). `master` sampled each 1-ft bin. */\n step?: number;\n /** Kernel smoothing (sigma 0.9 ft for makes/attempts, 1 for share, 0.6 for league; `:17`, `:76-77`); default true. */\n smooth?: boolean;\n /** No ribbon past the last bin with this many attempts, nor where the effective attempts fall below it; default 5 (`:19-21`). */\n minAttempts?: number;\n /** Colour prior in attempts (`shrunkDiff` k); default 25. 0 = the raw difference (`master`). */\n prior?: number;\n}\n\n/**\n * The shooting signature's samples: FG% from separately kernel-summed makes and attempts (a stretch with no\n * shots is a gap, never 0%), the smoothed league FG% and shot share, and the shrunk colour value (`:69-100`).\n * Input: 1-ft bins from `vsLeague(fgPctByDistance(player), fgPctByDistance(league))`.\n *\n * @example\n * ```ts\n * import { fgPctByDistance, signaturePoints, vsLeague } from \"@sportsdataverse/sdvplot/shots\";\n *\n * // Brooklyn shots from the sportsdataverse-data nba_stats_shots release, 2025-26\n * const made = { x_legacy: 0, y_legacy: 0, shot_distance: 0, shot_value: 2, shot_result: \"Made\" };\n * const missed = { ...made, shot_result: \"Missed\" };\n * signaturePoints(vsLeague(fgPctByDistance([made]), fgPctByDistance([made, missed])), { minAttempts: 1 })[0];\n * ```\n */\nexport function signaturePoints(\n bins: readonly DistanceVsLeague[],\n o: SignatureOptions = {},\n): SignaturePoint[] {\n const step = o.step ?? 0.25;\n if (!(step > 0)) throw new InputError(`signaturePoints step must be > 0, got ${String(step)}`);\n const smooth = o.smooth ?? true;\n const min = o.minAttempts ?? 5;\n const prior = o.prior ?? LEAGUE_PRIOR_ATTEMPTS;\n const attempts = kernelSum(\n bins.map((b) => b.attempts),\n smooth ? 0.9 : 0,\n );\n const makes = kernelSum(\n bins.map((b) => b.makes),\n smooth ? 0.9 : 0,\n );\n const share = kernelSmooth(\n bins.map((b) => b.share),\n null,\n smooth ? 1 : 0,\n );\n const league = kernelSmooth(\n bins.map((b) => b.leagueFgPct ?? 0),\n bins.map((b) => (b.leagueFgPct === null ? 0 : 1)),\n smooth ? 0.6 : 0,\n );\n let end: number | null = null; // ribbonEnd (`:54-57`); a loop, not findLast (ES2023, outside the ES2022 lib)\n for (let i = bins.length - 1; i >= 0; i--) {\n const b = bins[i];\n if (b !== undefined && b.attempts >= min) {\n end = b.distance;\n break;\n }\n }\n const last = bins.length - 1;\n const lerp = (a: readonly number[], d: number): number => {\n const i = Math.min(Math.floor(d), last);\n const j = Math.min(i + 1, last);\n const ai = a[i] ?? 0;\n return ai + ((a[j] ?? 0) - ai) * (d - i);\n };\n return Array.from({ length: Math.round(last / step) + 1 }, (_, k) => {\n const d = k * step;\n const a = lerp(attempts, d);\n const m = lerp(makes, d);\n const hasLeague = bins[Math.min(Math.round(d), last)]?.leagueFgPct != null;\n const leagueFgPct = hasLeague ? lerp(league, d) : null;\n const ok = end !== null && d <= end && a >= min;\n return {\n distance: d,\n fgPct: ok ? m / a : null,\n leagueFgPct,\n share: lerp(share, d),\n colourDiff: ok && leagueFgPct !== null ? shrunkDiff(m, a, leagueFgPct, prior) : null,\n };\n });\n}\n\n/**\n * What the Plot and d3 signatures share, so the two cannot drift (`main` `shootingSignature.ts:126-131`, `:183-193`):\n * the busiest drawn share, the last distance, the gradient's stops (drawn samples about half a foot apart, as\n * percentages of [0, maxFt]) and its id for a pixel x range. The stops are thinned by spacing, not by index: `main`'s\n * 0.25-ft samples keep every other one (`:192`), and `master`'s 1-ft samples keep every foot, as master drew them\n * (`ShootingSignature/index.js:52-67`). The gradient is in user space (pixels), so the id hashes that range\n * with the colours: the same signature at two widths never shares one, and two signatures never steal each other's\n * (`master`'s fixed id did, `Gradient.js:10`). Internal: not exported from `sdvplot/shots`.\n */\n/**\n * The signature ribbon's accessible name, shared by the Plot mark and its d3 twin.\n * @internal\n */\nexport const SIGNATURE_NAME = \"shooting signature: FG% by distance against the league\";\n\nexport function signatureGradient(\n points: readonly SignaturePoint[],\n fill: (diff: number | null) => string,\n): {\n readonly maxShare: number;\n readonly maxFt: number;\n readonly stops: readonly (readonly [offset: number, colour: string])[];\n readonly id: (x1: string, x2: string) => string;\n} {\n const drawn = points.filter((p) => p.fgPct !== null);\n const maxFt = points.at(-1)?.distance ?? 0;\n const step = (points[1]?.distance ?? 0) - (points[0]?.distance ?? 0);\n const every = step > 0 ? Math.max(1, Math.round(0.5 / step)) : 1; // 2 for main's 0.25 ft, 1 for master's 1 ft\n const stops = drawn\n .filter((_, i) => i % every === 0)\n .map((p) => [maxFt ? (p.distance / maxFt) * 100 : 0, fill(p.colourDiff)] as const);\n const text = stops.map((s) => s.join(\" \")).join(\";\");\n return {\n maxShare: Math.max(0, ...drawn.map((p) => p.share)) || 1,\n maxFt,\n stops,\n id: (x1, x2) => contentId(\"sdv-signature\", `${x1} ${x2}|${text}`),\n };\n}\n"]}
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
import { loadLeague, preloadAll, LEAGUES, resolveSync, getLeagueSync, MANIFEST_LAST_MODIFIED, INDEX_VERSION } from './chunk-ZAEQXFJM.js';
|
|
2
|
+
import { hex6, contrast } from './chunk-J6R2LEJ7.js';
|
|
3
|
+
import { InputError } from './chunk-E7G7VSLV.js';
|
|
4
|
+
|
|
5
|
+
// src/teams.ts
|
|
6
|
+
async function teams(league) {
|
|
7
|
+
if (league) return (await loadLeague(league)).teams;
|
|
8
|
+
await preloadAll();
|
|
9
|
+
return (await Promise.all(LEAGUES.map(loadLeague))).flatMap((d) => d.teams);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
// src/rows.ts
|
|
13
|
+
function rowsFrom(columns) {
|
|
14
|
+
const keys = Object.keys(columns);
|
|
15
|
+
const n = keys.length ? columns[keys[0]].length : 0;
|
|
16
|
+
for (const k of keys) {
|
|
17
|
+
const len = columns[k].length;
|
|
18
|
+
if (len !== n) throw new InputError(`rowsFrom: column "${k}" has length ${len}, expected ${n}`);
|
|
19
|
+
}
|
|
20
|
+
return Array.from({ length: n }, (_, i) => {
|
|
21
|
+
const row = {};
|
|
22
|
+
for (const k of keys) row[k] = columns[k][i];
|
|
23
|
+
return row;
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// src/matchup-colors.ts
|
|
28
|
+
var MIN_DELTA_E = 20;
|
|
29
|
+
var MIN_CONTRAST = 2.5;
|
|
30
|
+
var BACKGROUNDS = { light: "#ffffff", dark: "#181a1b" };
|
|
31
|
+
var NO_COLOR = "#2394fd";
|
|
32
|
+
function lab(hex) {
|
|
33
|
+
const [r, g, b] = [1, 3, 5].map((i) => {
|
|
34
|
+
const s = Number.parseInt(hex.slice(i, i + 2), 16) / 255;
|
|
35
|
+
return s > 0.04045 ? ((s + 0.055) / 1.055) ** 2.4 : s / 12.92;
|
|
36
|
+
});
|
|
37
|
+
const f = (t) => t > 8856e-6 ? t ** (1 / 3) : 7.787 * t + 16 / 116;
|
|
38
|
+
const x = f((r * 0.4124 + g * 0.3576 + b * 0.1805) / 0.95047);
|
|
39
|
+
const y = f(r * 0.2126 + g * 0.7152 + b * 0.0722);
|
|
40
|
+
const z = f((r * 0.0193 + g * 0.1192 + b * 0.9505) / 1.08883);
|
|
41
|
+
return [116 * y - 16, 500 * (x - y), 200 * (y - z)];
|
|
42
|
+
}
|
|
43
|
+
function withLightness(hex, L) {
|
|
44
|
+
const [, A, B] = lab(hex);
|
|
45
|
+
const f = (t) => t ** 3 > 8856e-6 ? t ** 3 : (t - 16 / 116) / 7.787;
|
|
46
|
+
const fy = (L + 16) / 116;
|
|
47
|
+
const x = 0.95047 * f(A / 500 + fy);
|
|
48
|
+
const y = f(fy);
|
|
49
|
+
const z = 1.08883 * f(fy - B / 200);
|
|
50
|
+
const lin = [
|
|
51
|
+
x * 3.2406 + y * -1.5372 + z * -0.4986,
|
|
52
|
+
x * -0.9689 + y * 1.8758 + z * 0.0415,
|
|
53
|
+
x * 0.0557 + y * -0.204 + z * 1.057
|
|
54
|
+
];
|
|
55
|
+
return `#${lin.map((v) => {
|
|
56
|
+
const s = v > 31308e-7 ? 1.055 * v ** (1 / 2.4) - 0.055 : 12.92 * v;
|
|
57
|
+
return Math.round(Math.max(0, Math.min(1, s)) * 255).toString(16).padStart(2, "0");
|
|
58
|
+
}).join("")}`;
|
|
59
|
+
}
|
|
60
|
+
function deltaE2000Lab([L1, a1, b1], [L2, a2, b2]) {
|
|
61
|
+
const rad = Math.PI / 180;
|
|
62
|
+
const Cm = (Math.hypot(a1, b1) + Math.hypot(a2, b2)) / 2;
|
|
63
|
+
const G = 0.5 * (1 - Math.sqrt(Cm ** 7 / (Cm ** 7 + 25 ** 7)));
|
|
64
|
+
const a1p = (1 + G) * a1;
|
|
65
|
+
const a2p = (1 + G) * a2;
|
|
66
|
+
const C1p = Math.hypot(a1p, b1);
|
|
67
|
+
const C2p = Math.hypot(a2p, b2);
|
|
68
|
+
const hue = (ap, bp) => ap === 0 && bp === 0 ? 0 : (Math.atan2(bp, ap) / rad + 360) % 360;
|
|
69
|
+
const h1 = hue(a1p, b1);
|
|
70
|
+
const h2 = hue(a2p, b2);
|
|
71
|
+
const dL = L2 - L1;
|
|
72
|
+
const dC = C2p - C1p;
|
|
73
|
+
let dh = 0;
|
|
74
|
+
if (C1p * C2p !== 0)
|
|
75
|
+
dh = Math.abs(h2 - h1) <= 180 ? h2 - h1 : h2 - h1 > 180 ? h2 - h1 - 360 : h2 - h1 + 360;
|
|
76
|
+
const dH = 2 * Math.sqrt(C1p * C2p) * Math.sin(dh * rad / 2);
|
|
77
|
+
const Lm = (L1 + L2) / 2;
|
|
78
|
+
const Cmp = (C1p + C2p) / 2;
|
|
79
|
+
let Hm = h1 + h2;
|
|
80
|
+
if (C1p * C2p !== 0)
|
|
81
|
+
Hm = Math.abs(h1 - h2) <= 180 ? (h1 + h2) / 2 : h1 + h2 < 360 ? (h1 + h2 + 360) / 2 : (h1 + h2 - 360) / 2;
|
|
82
|
+
const T = 1 - 0.17 * Math.cos((Hm - 30) * rad) + 0.24 * Math.cos(2 * Hm * rad) + 0.32 * Math.cos((3 * Hm + 6) * rad) - 0.2 * Math.cos((4 * Hm - 63) * rad);
|
|
83
|
+
const dTheta = 30 * Math.exp(-(((Hm - 275) / 25) ** 2));
|
|
84
|
+
const Rc = 2 * Math.sqrt(Cmp ** 7 / (Cmp ** 7 + 25 ** 7));
|
|
85
|
+
const Sl = 1 + 0.015 * (Lm - 50) ** 2 / Math.sqrt(20 + (Lm - 50) ** 2);
|
|
86
|
+
const Sc = 1 + 0.045 * Cmp;
|
|
87
|
+
const Sh = 1 + 0.015 * Cmp * T;
|
|
88
|
+
const Rt = -Math.sin(2 * dTheta * rad) * Rc;
|
|
89
|
+
return Math.sqrt((dL / Sl) ** 2 + (dC / Sc) ** 2 + (dH / Sh) ** 2 + Rt * (dC / Sc) * (dH / Sh));
|
|
90
|
+
}
|
|
91
|
+
function deltaE2000(a, b) {
|
|
92
|
+
return deltaE2000Lab(lab(a), lab(b));
|
|
93
|
+
}
|
|
94
|
+
function pickPair(candidates, background) {
|
|
95
|
+
const readable = (c) => contrast(c, background) >= MIN_CONTRAST;
|
|
96
|
+
const apart = (x2, y2) => deltaE2000(x2, y2) >= MIN_DELTA_E;
|
|
97
|
+
const dir = lab(background)[0] >= 50 ? -1 : 1;
|
|
98
|
+
const readableVariant = (c) => {
|
|
99
|
+
if (readable(c)) return c;
|
|
100
|
+
const L0 = lab(c)[0];
|
|
101
|
+
for (let k = 1; k <= 100; k++) {
|
|
102
|
+
const v = withLightness(c, L0 + dir * k);
|
|
103
|
+
if (readable(v)) return v;
|
|
104
|
+
}
|
|
105
|
+
return c;
|
|
106
|
+
};
|
|
107
|
+
for (const [x2, y2] of candidates) if (readable(x2) && readable(y2) && apart(x2, y2)) return [x2, y2];
|
|
108
|
+
const variants = candidates.map(([x2, y2]) => [readableVariant(x2), readableVariant(y2)]);
|
|
109
|
+
for (const [x2, y2] of variants) if (apart(x2, y2)) return [x2, y2];
|
|
110
|
+
const [x, y] = variants[0];
|
|
111
|
+
const readableShifts = (c) => {
|
|
112
|
+
const L0 = lab(c)[0];
|
|
113
|
+
const byShift = [];
|
|
114
|
+
for (let k = -100; k <= 100; k++) {
|
|
115
|
+
if (L0 + k < 0 || L0 + k > 100) continue;
|
|
116
|
+
const v = k === 0 ? c : withLightness(c, L0 + k);
|
|
117
|
+
if (!readable(v)) continue;
|
|
118
|
+
byShift[Math.abs(k)] ??= [];
|
|
119
|
+
byShift[Math.abs(k)]?.push(v);
|
|
120
|
+
}
|
|
121
|
+
return byShift;
|
|
122
|
+
};
|
|
123
|
+
const xs = readableShifts(x);
|
|
124
|
+
const ys = readableShifts(y);
|
|
125
|
+
let best = [x, y];
|
|
126
|
+
let bestDE = deltaE2000(x, y);
|
|
127
|
+
for (let total = 1; total <= 200; total++) {
|
|
128
|
+
for (let i = 0; i <= total; i++) {
|
|
129
|
+
for (const hx of xs[i] ?? [])
|
|
130
|
+
for (const hy of ys[total - i] ?? []) {
|
|
131
|
+
const d = deltaE2000(hx, hy);
|
|
132
|
+
if (d >= MIN_DELTA_E) return [hx, hy];
|
|
133
|
+
if (d > bestDE) {
|
|
134
|
+
best = [hx, hy];
|
|
135
|
+
bestDE = d;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
return best;
|
|
141
|
+
}
|
|
142
|
+
function matchupColorsSync(teamA, teamB, opts) {
|
|
143
|
+
const { league, theme, ...resolveOpts } = opts;
|
|
144
|
+
const bg = { light: hex6(theme?.light ?? BACKGROUNDS.light), dark: hex6(theme?.dark ?? BACKGROUNDS.dark) };
|
|
145
|
+
const ids = resolveSync([teamA, teamB], league, resolveOpts);
|
|
146
|
+
const rows = getLeagueSync(league).teams;
|
|
147
|
+
const [a, b] = ids.map((id) => {
|
|
148
|
+
const t = id === void 0 ? void 0 : rows.find((r) => r.team_id === id);
|
|
149
|
+
const alt = t?.color_secondary ?? null;
|
|
150
|
+
return { primary: t?.color_primary ?? alt ?? NO_COLOR, alt };
|
|
151
|
+
});
|
|
152
|
+
const candidates = [
|
|
153
|
+
[a.primary, b.primary],
|
|
154
|
+
[a.primary, b.alt],
|
|
155
|
+
[a.alt, b.primary],
|
|
156
|
+
[a.alt, b.alt]
|
|
157
|
+
].filter((p) => p[0] !== null && p[1] !== null);
|
|
158
|
+
return { light: pickPair(candidates, bg.light), dark: pickPair(candidates, bg.dark) };
|
|
159
|
+
}
|
|
160
|
+
async function matchupColors(teamA, teamB, opts) {
|
|
161
|
+
await loadLeague(opts.league);
|
|
162
|
+
return matchupColorsSync(teamA, teamB, opts);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// src/version.ts
|
|
166
|
+
var VERSION = "0.2.0";
|
|
167
|
+
|
|
168
|
+
// src/versions.ts
|
|
169
|
+
function versions() {
|
|
170
|
+
return { sdvplot: VERSION, index: INDEX_VERSION, manifestLastModified: MANIFEST_LAST_MODIFIED };
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
export { VERSION, matchupColors, matchupColorsSync, rowsFrom, teams, versions };
|
|
174
|
+
//# sourceMappingURL=chunk-J4P4HDBF.js.map
|
|
175
|
+
//# sourceMappingURL=chunk-J4P4HDBF.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/teams.ts","../src/rows.ts","../src/matchup-colors.ts","../src/version.ts","../src/versions.ts"],"names":["x","y"],"mappings":"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","file":"chunk-J4P4HDBF.js","sourcesContent":["import { LEAGUES, type League, type Team } from \"./data/index.js\";\nimport { loadLeague, preloadAll } from \"./index-data.js\";\n\n/** All teams for one league, or every league (in `LEAGUES` order) when omitted. */\nexport async function teams(league?: League): Promise<readonly Team[]> {\n if (league) return (await loadLeague(league)).teams;\n await preloadAll();\n return (await Promise.all(LEAGUES.map(loadLeague))).flatMap((d) => d.teams);\n}\n","import { InputError } from \"./errors.js\";\n\n/** Column form → `Row[]` (flat objects, one per index). Columns must be equal length; `{}` → `[]`. */\nexport function rowsFrom(columns: Record<string, readonly unknown[]>): Record<string, unknown>[] {\n const keys = Object.keys(columns);\n const n = keys.length ? (columns[keys[0] as string] as readonly unknown[]).length : 0;\n for (const k of keys) {\n const len = (columns[k] as readonly unknown[]).length;\n if (len !== n) throw new InputError(`rowsFrom: column \"${k}\" has length ${len}, expected ${n}`);\n }\n return Array.from({ length: n }, (_, i) => {\n const row: Record<string, unknown> = {};\n for (const k of keys) row[k] = (columns[k] as readonly unknown[])[i];\n return row;\n });\n}\n","import { contrast, hex6 } from \"./contrast.js\";\nimport { getLeagueSync, loadLeague } from \"./index-data.js\";\nimport { type ResolveOptions, type Value, resolveSync } from \"./resolve.js\";\nimport type { League } from \"./types.js\";\n\n// Port of Game on Paper's game colours (`pickGameColors`, astro/src/utils/misc.ts at b8b8eaad): its Lab\n// conversion, candidate order, thresholds and fallbacks, kept operation for operation so the pairs match\n// its output exactly (fixtures/matchup-colors).\n\nconst MIN_DELTA_E = 20;\nconst MIN_CONTRAST = 2.5;\n/** Game on Paper's page backgrounds (`GAME_BACKGROUNDS`). */\nconst BACKGROUNDS = { light: \"#ffffff\", dark: \"#181a1b\" } as const;\n/** A team with no colour gets Game on Paper's `STANDARD_THEME_COLOR`. */\nconst NO_COLOR = \"#2394fd\";\n\ntype Lab = [number, number, number];\n\n/** CIE L*a*b* (D65) of `#rrggbb`: Game on Paper's `rgb2lab`, with its rounded constants. */\nfunction lab(hex: string): Lab {\n const [r, g, b] = [1, 3, 5].map((i) => {\n const s = Number.parseInt(hex.slice(i, i + 2), 16) / 255;\n return s > 0.04045 ? ((s + 0.055) / 1.055) ** 2.4 : s / 12.92;\n }) as Lab;\n const f = (t: number) => (t > 0.008856 ? t ** (1 / 3) : 7.787 * t + 16 / 116);\n const x = f((r * 0.4124 + g * 0.3576 + b * 0.1805) / 0.95047);\n const y = f(r * 0.2126 + g * 0.7152 + b * 0.0722);\n const z = f((r * 0.0193 + g * 0.1192 + b * 0.9505) / 1.08883);\n return [116 * y - 16, 500 * (x - y), 200 * (y - z)];\n}\n\n/** `hex` with its L* set to `L` (a*, b* kept), clamped into sRGB: Game on Paper's `lab2rgb` + `rgbArrayToHex`. */\nfunction withLightness(hex: string, L: number): string {\n const [, A, B] = lab(hex);\n const f = (t: number) => (t ** 3 > 0.008856 ? t ** 3 : (t - 16 / 116) / 7.787);\n const fy = (L + 16) / 116;\n const x = 0.95047 * f(A / 500 + fy);\n const y = f(fy);\n const z = 1.08883 * f(fy - B / 200);\n const lin = [\n x * 3.2406 + y * -1.5372 + z * -0.4986,\n x * -0.9689 + y * 1.8758 + z * 0.0415,\n x * 0.0557 + y * -0.204 + z * 1.057,\n ];\n return `#${lin\n .map((v) => {\n const s = v > 0.0031308 ? 1.055 * v ** (1 / 2.4) - 0.055 : 12.92 * v;\n return Math.round(Math.max(0, Math.min(1, s)) * 255)\n .toString(16)\n .padStart(2, \"0\");\n })\n .join(\"\")}`;\n}\n\n/** CIEDE2000 colour difference between two L*a*b* colours (Sharma, Wu and Dalal 2005). */\nexport function deltaE2000Lab([L1, a1, b1]: Lab, [L2, a2, b2]: Lab): number {\n const rad = Math.PI / 180;\n const Cm = (Math.hypot(a1, b1) + Math.hypot(a2, b2)) / 2;\n const G = 0.5 * (1 - Math.sqrt(Cm ** 7 / (Cm ** 7 + 25 ** 7)));\n const a1p = (1 + G) * a1;\n const a2p = (1 + G) * a2;\n const C1p = Math.hypot(a1p, b1);\n const C2p = Math.hypot(a2p, b2);\n const hue = (ap: number, bp: number) => (ap === 0 && bp === 0 ? 0 : (Math.atan2(bp, ap) / rad + 360) % 360);\n const h1 = hue(a1p, b1);\n const h2 = hue(a2p, b2);\n const dL = L2 - L1;\n const dC = C2p - C1p;\n let dh = 0;\n if (C1p * C2p !== 0)\n dh = Math.abs(h2 - h1) <= 180 ? h2 - h1 : h2 - h1 > 180 ? h2 - h1 - 360 : h2 - h1 + 360;\n const dH = 2 * Math.sqrt(C1p * C2p) * Math.sin((dh * rad) / 2);\n const Lm = (L1 + L2) / 2;\n const Cmp = (C1p + C2p) / 2;\n let Hm = h1 + h2;\n if (C1p * C2p !== 0)\n Hm = Math.abs(h1 - h2) <= 180 ? (h1 + h2) / 2 : h1 + h2 < 360 ? (h1 + h2 + 360) / 2 : (h1 + h2 - 360) / 2;\n const T =\n 1 -\n 0.17 * Math.cos((Hm - 30) * rad) +\n 0.24 * Math.cos(2 * Hm * rad) +\n 0.32 * Math.cos((3 * Hm + 6) * rad) -\n 0.2 * Math.cos((4 * Hm - 63) * rad);\n const dTheta = 30 * Math.exp(-(((Hm - 275) / 25) ** 2));\n const Rc = 2 * Math.sqrt(Cmp ** 7 / (Cmp ** 7 + 25 ** 7));\n const Sl = 1 + (0.015 * (Lm - 50) ** 2) / Math.sqrt(20 + (Lm - 50) ** 2);\n const Sc = 1 + 0.045 * Cmp;\n const Sh = 1 + 0.015 * Cmp * T;\n const Rt = -Math.sin(2 * dTheta * rad) * Rc;\n return Math.sqrt((dL / Sl) ** 2 + (dC / Sc) ** 2 + (dH / Sh) ** 2 + Rt * (dC / Sc) * (dH / Sh));\n}\n\n/** CIEDE2000 between two `#rrggbb` colours, through Game on Paper's Lab conversion. */\nexport function deltaE2000(a: string, b: string): number {\n return deltaE2000Lab(lab(a), lab(b));\n}\n\n/** Game on Paper's `pickPairOn`: the first usable candidate, else L* moved until the pair reads and separates. */\nfunction pickPair(candidates: [string, string][], background: string): [string, string] {\n const readable = (c: string) => contrast(c, background) >= MIN_CONTRAST;\n const apart = (x: string, y: string) => deltaE2000(x, y) >= MIN_DELTA_E;\n // the side with room to read: darker on a light background, lighter on a dark one\n const dir = lab(background)[0] >= 50 ? -1 : 1;\n // nearest L* at which the colour reads on this background\n const readableVariant = (c: string) => {\n if (readable(c)) return c;\n const L0 = lab(c)[0];\n for (let k = 1; k <= 100; k++) {\n const v = withLightness(c, L0 + dir * k);\n if (readable(v)) return v;\n }\n return c;\n };\n\n for (const [x, y] of candidates) if (readable(x) && readable(y) && apart(x, y)) return [x, y];\n const variants = candidates.map(([x, y]): [string, string] => [readableVariant(x), readableVariant(y)]);\n for (const [x, y] of variants) if (apart(x, y)) return [x, y];\n\n // move both colours' L* within the range where each still reads, least total movement first;\n // keep the widest readable separation seen in case nothing clears\n const [x, y] = variants[0] as [string, string];\n const readableShifts = (c: string) => {\n const L0 = lab(c)[0];\n const byShift: string[][] = []; // byShift[|k|] = readable variants moved |k| in L*\n for (let k = -100; k <= 100; k++) {\n if (L0 + k < 0 || L0 + k > 100) continue;\n const v = k === 0 ? c : withLightness(c, L0 + k);\n if (!readable(v)) continue;\n byShift[Math.abs(k)] ??= [];\n byShift[Math.abs(k)]?.push(v);\n }\n return byShift;\n };\n const xs = readableShifts(x);\n const ys = readableShifts(y);\n let best: [string, string] = [x, y];\n let bestDE = deltaE2000(x, y);\n for (let total = 1; total <= 200; total++) {\n for (let i = 0; i <= total; i++) {\n for (const hx of xs[i] ?? [])\n for (const hy of ys[total - i] ?? []) {\n const d = deltaE2000(hx, hy);\n if (d >= MIN_DELTA_E) return [hx, hy];\n if (d > bestDE) {\n best = [hx, hy];\n bestDE = d;\n }\n }\n }\n }\n return best;\n}\n\n/** Options for {@link matchupColors}: the league, how to resolve the two teams, and the backgrounds. */\nexport interface MatchupColorOptions extends ResolveOptions {\n /** The league both teams belong to. */\n league: League;\n /** The background each theme's pair must read on; default `{ light: \"#ffffff\", dark: \"#181a1b\" }` (Game on Paper's pages). */\n theme?: { light?: string; dark?: string };\n}\n/** One `[teamA, teamB]` colour pair per theme, as lowercase `#rrggbb`. */\nexport interface MatchupColors {\n /** The pair for a light background. */\n light: [string, string];\n /** The pair for a dark background. */\n dark: [string, string];\n}\n\n/**\n * {@link matchupColors} once the league is loaded (`loadLeague` or `preloadAll`).\n *\n * @example\n * ```ts\n * import { loadLeague, matchupColorsSync } from \"@sportsdataverse/sdvplot\";\n *\n * await loadLeague(\"cfb\");\n * matchupColorsSync(\"Alabama\", \"Georgia\", { league: \"cfb\" });\n * ```\n */\nexport function matchupColorsSync(teamA: Value, teamB: Value, opts: MatchupColorOptions): MatchupColors {\n const { league, theme, ...resolveOpts } = opts;\n const bg = { light: hex6(theme?.light ?? BACKGROUNDS.light), dark: hex6(theme?.dark ?? BACKGROUNDS.dark) };\n const ids = resolveSync([teamA, teamB], league, resolveOpts);\n const rows = getLeagueSync(league).teams;\n const [a, b] = ids.map((id) => {\n const t = id === undefined ? undefined : rows.find((r) => r.team_id === id);\n const alt = t?.color_secondary ?? null;\n return { primary: t?.color_primary ?? alt ?? NO_COLOR, alt };\n }) as [{ primary: string; alt: string | null }, { primary: string; alt: string | null }];\n // both primaries, teamB's alternate, teamA's alternate, both alternates\n const candidates = [\n [a.primary, b.primary],\n [a.primary, b.alt],\n [a.alt, b.primary],\n [a.alt, b.alt],\n ].filter((p): p is [string, string] => p[0] !== null && p[1] !== null);\n return { light: pickPair(candidates, bg.light), dark: pickPair(candidates, bg.dark) };\n}\n\n/**\n * Two team colours that tell the teams apart on a chart: Game on Paper's \"game colours\" picker.\n *\n * @remarks\n * One pair per theme, each the first candidate whose colours are at least 20 apart in CIEDE2000 and each\n * at least 2.5:1 (WCAG) against that theme's background. Candidates, in order: both primaries, teamB's\n * secondary, teamA's secondary, both secondaries; so a team keeps its primary whenever it works, and teamA\n * (Game on Paper's home team) keeps it first. When no candidate works, an unreadable colour's L* moves just\n * far enough to read; then both colours' L* move, least total movement first, until they separate; failing\n * that, the widest readable separation found. A team with no colour gets `#2394fd`. Deterministic.\n *\n * @example\n * ```ts\n * import { matchupColors } from \"@sportsdataverse/sdvplot\";\n *\n * // two crimson teams; light: [\"#9e1b32\", \"#2c2a29\"] (Georgia's secondary), dark: [\"#ffffff\", \"#ba0c2f\"]\n * // in a page, pick the pair for the theme:\n * // const [alabama, georgia] = window.matchMedia(\"(prefers-color-scheme: dark)\").matches ? dark : light;\n * await matchupColors(\"Alabama\", \"Georgia\", { league: \"cfb\" });\n * ```\n */\nexport async function matchupColors(\n teamA: Value,\n teamB: Value,\n opts: MatchupColorOptions,\n): Promise<MatchupColors> {\n await loadLeague(opts.league);\n return matchupColorsSync(teamA, teamB, opts);\n}\n","// Injected from package.json by tsup/vitest `define`, so changesets version bumps reach versions().\nexport const VERSION: string = __SDVPLOT_VERSION__;\n","import { INDEX_VERSION, MANIFEST_LAST_MODIFIED } from \"./data/index.js\";\nimport { VERSION } from \"./version.js\";\n\nexport function versions(): { sdvplot: string; index: string; manifestLastModified: string } {\n return { sdvplot: VERSION, index: INDEX_VERSION, manifestLastModified: MANIFEST_LAST_MODIFIED };\n}\n"]}
|