@sportsdataverse/sdvplot 0.0.0-stage → 0.1.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 +94 -0
- package/README.md +908 -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 +132 -0
- package/dist/chartjs.js +339 -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-C2CDY5OG.js +137 -0
- package/dist/chunk-C2CDY5OG.js.map +1 -0
- package/dist/chunk-E7G7VSLV.js +43 -0
- package/dist/chunk-E7G7VSLV.js.map +1 -0
- package/dist/chunk-J6R2LEJ7.js +58 -0
- package/dist/chunk-J6R2LEJ7.js.map +1 -0
- package/dist/chunk-JJFTD4VB.js +175 -0
- package/dist/chunk-JJFTD4VB.js.map +1 -0
- package/dist/chunk-KKH6ABMA.js +223 -0
- package/dist/chunk-KKH6ABMA.js.map +1 -0
- package/dist/chunk-NLPH2VZT.js +127 -0
- package/dist/chunk-NLPH2VZT.js.map +1 -0
- package/dist/chunk-OKEE4AH6.js +30 -0
- package/dist/chunk-OKEE4AH6.js.map +1 -0
- package/dist/chunk-QY65DUEF.js +306 -0
- package/dist/chunk-QY65DUEF.js.map +1 -0
- package/dist/chunk-RMT2GMFH.js +97 -0
- package/dist/chunk-RMT2GMFH.js.map +1 -0
- package/dist/chunk-SEBO35DV.js +76 -0
- package/dist/chunk-SEBO35DV.js.map +1 -0
- package/dist/chunk-TAXO3VXH.js +556 -0
- package/dist/chunk-TAXO3VXH.js.map +1 -0
- package/dist/chunk-TDNBAEQB.js +105 -0
- package/dist/chunk-TDNBAEQB.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/d3.d.ts +234 -0
- package/dist/d3.js +198 -0
- package/dist/d3.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/headshots-B2J_LOO8.d.ts +40 -0
- package/dist/index.d.ts +182 -0
- package/dist/index.js +10 -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/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/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/ohl-2FEM554G.js +113 -0
- package/dist/ohl-C5Q4W6JB.js +32 -0
- package/dist/ohl-TQ4J46VC.js +66 -0
- package/dist/phf-H7POU2KW.js +32 -0
- package/dist/phf-K5IFMZUM.js +13 -0
- package/dist/phf-TWJI3BOW.js +20 -0
- package/dist/plot.d.ts +449 -0
- package/dist/plot.js +711 -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 +101 -0
- package/dist/react.js +164 -0
- package/dist/react.js.map +1 -0
- package/dist/resolve-CQW-jzvr.d.ts +28 -0
- package/dist/selection-D4T3PkRt.d.ts +140 -0
- package/dist/shots.d.ts +364 -0
- package/dist/shots.js +85 -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/surfaces-ChaD9nei.d.ts +38 -0
- package/dist/testing.d.ts +75 -0
- package/dist/testing.js +240 -0
- package/dist/testing.js.map +1 -0
- package/dist/tiers-B6pUBPcE.d.ts +93 -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/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/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 +154 -3
package/dist/plot.js
ADDED
|
@@ -0,0 +1,711 @@
|
|
|
1
|
+
import { prepareTiers } from './chunk-NLPH2VZT.js';
|
|
2
|
+
import { toId } from './chunk-4C3GX6QO.js';
|
|
3
|
+
import { stampImage, teamColorDomain } from './chunk-OKEE4AH6.js';
|
|
4
|
+
import { diffScale, LEAGUE_PRIOR_ATTEMPTS, signatureGradient, SIGNATURE_NAME, shrunkDiff } from './chunk-QY65DUEF.js';
|
|
5
|
+
import { cellPoints } from './chunk-C2CDY5OG.js';
|
|
6
|
+
import { checkHeight, checkAlpha, placeSync, placedName } from './chunk-RMT2GMFH.js';
|
|
7
|
+
import { logoUrlSync } from './chunk-KKH6ABMA.js';
|
|
8
|
+
import { surfaceScene } from './chunk-TDNBAEQB.js';
|
|
9
|
+
export { SURFACES, SURFACE_BASE, colorUpdates } from './chunk-TDNBAEQB.js';
|
|
10
|
+
import { hex6 } from './chunk-J6R2LEJ7.js';
|
|
11
|
+
import './chunk-TAXO3VXH.js';
|
|
12
|
+
import { InputError, warn } from './chunk-E7G7VSLV.js';
|
|
13
|
+
import * as Plot6 from '@observablehq/plot';
|
|
14
|
+
import { surfaceMark, surfaceScales } from '@sportsdataverse/sporty/plot';
|
|
15
|
+
import { BASKETBALL_ZONE_LABELS, FRAMES } from '@sportsdataverse/sporty';
|
|
16
|
+
|
|
17
|
+
var once = (data) => Symbol.iterator in data && !Array.isArray(data) && !("numRows" in data) ? Array.from(data) : data;
|
|
18
|
+
var values = (data, c) => Plot6.valueof(data, c);
|
|
19
|
+
function given(data, c) {
|
|
20
|
+
const v = typeof c === "object" && c !== null && !Array.isArray(c) && "value" in c ? c.value : c;
|
|
21
|
+
return typeof v === "string" || typeof v === "function" || Array.isArray(v) ? values(data, v) : null;
|
|
22
|
+
}
|
|
23
|
+
function dataValue(values2, key, i) {
|
|
24
|
+
const ch = values2.channels[key];
|
|
25
|
+
const src = ch?.source ?? (ch?.scale == null ? void 0 : ch);
|
|
26
|
+
const v = src?.value[i];
|
|
27
|
+
return v === void 0 || v === null ? null : String(v instanceof Date ? v.getTime() : v);
|
|
28
|
+
}
|
|
29
|
+
function sizeRender(height, placed, kind, ids) {
|
|
30
|
+
const byRow = new Map(placed.map((p) => [p.index, p]));
|
|
31
|
+
return (index, scales, vals, dimensions, context, next) => {
|
|
32
|
+
const g = next?.(index, scales, vals, dimensions, context);
|
|
33
|
+
if (g == null) return null;
|
|
34
|
+
const { width, height: H, marginTop, marginRight, marginBottom, marginLeft } = dimensions;
|
|
35
|
+
const frame = H - marginTop - marginBottom;
|
|
36
|
+
const px = height * frame;
|
|
37
|
+
const X = vals.x;
|
|
38
|
+
const Y = vals.y;
|
|
39
|
+
const images = g.querySelectorAll("image");
|
|
40
|
+
index.forEach((i, k) => {
|
|
41
|
+
const img = images[k];
|
|
42
|
+
const p = byRow.get(i);
|
|
43
|
+
if (!img || !p) return;
|
|
44
|
+
const cx = X ? X[i] : (marginLeft + width - marginRight) / 2;
|
|
45
|
+
const cy = Y ? Y[i] : (marginTop + H - marginBottom) / 2;
|
|
46
|
+
stampImage(img, p, kind, px, frame, cx, cy);
|
|
47
|
+
img.setAttribute("data-sdv-id", ids?.[i] ?? p.id);
|
|
48
|
+
const dx = dataValue(vals, "x", i);
|
|
49
|
+
const dy = dataValue(vals, "y", i);
|
|
50
|
+
if (dx !== null) img.setAttribute("data-sdv-x", dx);
|
|
51
|
+
if (dy !== null) img.setAttribute("data-sdv-y", dy);
|
|
52
|
+
});
|
|
53
|
+
return g;
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
function compose(outer, inner) {
|
|
57
|
+
if (outer === void 0) return inner;
|
|
58
|
+
return function(index, scales, values2, dimensions, context, next) {
|
|
59
|
+
return outer.call(
|
|
60
|
+
this,
|
|
61
|
+
index,
|
|
62
|
+
scales,
|
|
63
|
+
values2,
|
|
64
|
+
dimensions,
|
|
65
|
+
context,
|
|
66
|
+
(i, s, v, d, c) => inner.call(this, i, s, v, d, c, next)
|
|
67
|
+
);
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
var ONE_PER_ROW = "sdvplot marks draw one shape per input row (an image, a cell, a zone), so a transform that makes new rows (bin, group, hexbin) cannot run on them: aggregate first, then draw the result";
|
|
71
|
+
function sameRows(f) {
|
|
72
|
+
return function(data, facets, ...rest) {
|
|
73
|
+
const out = f.call(this, data, facets, ...rest);
|
|
74
|
+
if (out.data !== void 0 && out.data !== data) throw new InputError(ONE_PER_ROW);
|
|
75
|
+
const n = Array.isArray(data) ? data.length : data?.numRows;
|
|
76
|
+
for (const c of Object.values(out.channels ?? {}))
|
|
77
|
+
if (c?.value != null && c.value.length !== n) throw new InputError(ONE_PER_ROW);
|
|
78
|
+
out.facets?.forEach((I, k) => {
|
|
79
|
+
const own = new Set(facets?.[k]);
|
|
80
|
+
for (const i of I) if (!own.has(i)) throw new InputError(ONE_PER_ROW);
|
|
81
|
+
});
|
|
82
|
+
return out;
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
function imageMark(input, o, kind, key) {
|
|
86
|
+
const data = once(input);
|
|
87
|
+
const height = checkHeight(o.height ?? 0.1);
|
|
88
|
+
if (o.alpha !== void 0 && o.opacity !== void 0)
|
|
89
|
+
throw new InputError("pass alpha (a constant) or opacity (Plot's channel), not both");
|
|
90
|
+
const alpha = checkAlpha(o.alpha ?? 1);
|
|
91
|
+
const keys = values(data, key);
|
|
92
|
+
const ids = o.id == null ? void 0 : values(data, o.id).map(toId);
|
|
93
|
+
const name = kind === "headshot" ? "player" : "team";
|
|
94
|
+
if (ids !== void 0 && ids.length !== keys.length)
|
|
95
|
+
throw new InputError(
|
|
96
|
+
`id and ${name} need one value per row: id has ${ids.length}, ${name} has ${keys.length}`
|
|
97
|
+
);
|
|
98
|
+
const zeros = keys.map(() => 0);
|
|
99
|
+
const xs = given(data, o.x) ?? zeros;
|
|
100
|
+
const ys = given(data, o.y) ?? zeros;
|
|
101
|
+
const season = o.season == null || typeof o.season === "number" || typeof o.season === "string" && /^\d{4}$/.test(o.season) ? o.season : values(data, o.season);
|
|
102
|
+
const placed = placeSync(xs, ys, keys, {
|
|
103
|
+
league: o.league,
|
|
104
|
+
kind,
|
|
105
|
+
...kind === "headshot" ? {} : { variant: o.variant ?? "default" },
|
|
106
|
+
...o.idSystem === void 0 ? kind === "headshot" ? { idSystem: "espn" } : {} : { idSystem: o.idSystem },
|
|
107
|
+
...season === void 0 || kind === "headshot" ? {} : { season }
|
|
108
|
+
});
|
|
109
|
+
const src = new Array(keys.length).fill(null);
|
|
110
|
+
for (const p of placed) src[p.index] = p.url;
|
|
111
|
+
const names = keys.map((k) => String(k));
|
|
112
|
+
for (const p of placed) names[p.index] = placedName(p, o.league, keys[p.index]);
|
|
113
|
+
const {
|
|
114
|
+
height: _h,
|
|
115
|
+
alpha: _a,
|
|
116
|
+
r: _r,
|
|
117
|
+
league: _l,
|
|
118
|
+
season: _s,
|
|
119
|
+
variant: _v,
|
|
120
|
+
idSystem: _i,
|
|
121
|
+
id: _id,
|
|
122
|
+
// sdvplot's link id, stamped by sizeRender: never Plot's
|
|
123
|
+
render,
|
|
124
|
+
transform,
|
|
125
|
+
initializer: initializer2,
|
|
126
|
+
channels,
|
|
127
|
+
...rest
|
|
128
|
+
} = o;
|
|
129
|
+
const { team: _t, player: _p, ...pass } = rest;
|
|
130
|
+
return Plot6.image(data, {
|
|
131
|
+
...pass,
|
|
132
|
+
src,
|
|
133
|
+
...o.opacity === void 0 ? { opacity: alpha } : {},
|
|
134
|
+
// an accessible name per image, as the Vega adapter's (PR #28): "KC logo", "3139477 headshot"
|
|
135
|
+
ariaLabel: o.ariaLabel ?? names.map((n) => `${n} ${kind}`),
|
|
136
|
+
// the tip names the team as the image does ("KC", not team_id "12"); a headshot has only its id
|
|
137
|
+
channels: { [name]: { value: kind === "headshot" ? keys : names, label: name }, ...channels },
|
|
138
|
+
...transform === void 0 ? {} : { transform: sameRows(transform) },
|
|
139
|
+
...initializer2 === void 0 ? {} : { initializer: sameRows(initializer2) },
|
|
140
|
+
render: compose(render, sizeRender(height, placed, kind, ids))
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
function logos(data, o) {
|
|
144
|
+
return imageMark(data, o, "logo", o.team);
|
|
145
|
+
}
|
|
146
|
+
function wordmarks(data, o) {
|
|
147
|
+
return imageMark(data, o, "wordmark", o.team);
|
|
148
|
+
}
|
|
149
|
+
function headshots(data, o) {
|
|
150
|
+
return imageMark(data, o, "headshot", o.player);
|
|
151
|
+
}
|
|
152
|
+
function stampRender(idOf, mismatch) {
|
|
153
|
+
return (index, scales, values2, dimensions, context, next) => {
|
|
154
|
+
const g = next?.(index, scales, values2, dimensions, context) ?? null;
|
|
155
|
+
if (g === null) return null;
|
|
156
|
+
const els = [];
|
|
157
|
+
for (let el = g.firstElementChild; el !== null; el = el.nextElementSibling) els.push(el);
|
|
158
|
+
if (els.length !== index.length) {
|
|
159
|
+
mismatch?.(els.length, index.length);
|
|
160
|
+
return g;
|
|
161
|
+
}
|
|
162
|
+
const at = (el) => el?.tagName === "a" ? el.querySelector("[data-sdv-id]") ?? el : el;
|
|
163
|
+
index.forEach((i, k) => at(els[k])?.setAttribute("data-sdv-id", idOf(i)));
|
|
164
|
+
return g;
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
function linkIds(data, id) {
|
|
168
|
+
const ids = id === void 0 ? null : Array.from(Plot6.valueof(data, id) ?? [], toId);
|
|
169
|
+
return stampRender(
|
|
170
|
+
(i) => ids === null ? String(i) : ids[i] ?? "",
|
|
171
|
+
(drawn, rows) => warn(
|
|
172
|
+
`linkIds:${drawn}:${rows}`,
|
|
173
|
+
`linkIds needs one element per row; this mark drew ${drawn} for ${rows} rows, so nothing was stamped`
|
|
174
|
+
)
|
|
175
|
+
);
|
|
176
|
+
}
|
|
177
|
+
var DEFAULT_ANCHOR = { x: "bottom", y: "left", fx: "top", fy: "right" };
|
|
178
|
+
var AXIS = {
|
|
179
|
+
x: Plot6.axisX,
|
|
180
|
+
y: Plot6.axisY,
|
|
181
|
+
fx: Plot6.axisFx,
|
|
182
|
+
fy: Plot6.axisFy
|
|
183
|
+
};
|
|
184
|
+
function axisLogos(axis, o) {
|
|
185
|
+
const {
|
|
186
|
+
league: _l,
|
|
187
|
+
season: _s,
|
|
188
|
+
height: _h,
|
|
189
|
+
variant: _v,
|
|
190
|
+
markType: _m,
|
|
191
|
+
idSystem: _i,
|
|
192
|
+
anchor: _a,
|
|
193
|
+
tickSize: _t,
|
|
194
|
+
label: _b,
|
|
195
|
+
render: outer,
|
|
196
|
+
...pass
|
|
197
|
+
} = o;
|
|
198
|
+
const height = checkHeight(o.height ?? 0.1);
|
|
199
|
+
const kind = o.markType ?? "logo";
|
|
200
|
+
const tickSize = o.tickSize ?? 6;
|
|
201
|
+
const horizontal = axis === "x" || axis === "fx";
|
|
202
|
+
const side = o.anchor ?? DEFAULT_ANCHOR[axis];
|
|
203
|
+
const render = (index, scales, values2, dimensions, context, next) => {
|
|
204
|
+
const g = next?.(index, scales, values2, dimensions, context);
|
|
205
|
+
const text3 = values2.text;
|
|
206
|
+
if (!g || !text3) return g ?? null;
|
|
207
|
+
const labels = Array.from(index, (i) => text3[i] ?? "");
|
|
208
|
+
const placed = placeSync(
|
|
209
|
+
labels.map(() => 0),
|
|
210
|
+
labels.map(() => 0),
|
|
211
|
+
labels,
|
|
212
|
+
{
|
|
213
|
+
league: o.league,
|
|
214
|
+
kind,
|
|
215
|
+
variant: o.variant ?? "default",
|
|
216
|
+
...o.idSystem === void 0 ? {} : { idSystem: o.idSystem },
|
|
217
|
+
...o.season === void 0 ? {} : { season: o.season }
|
|
218
|
+
}
|
|
219
|
+
);
|
|
220
|
+
const byIndex = new Map(placed.map((p) => [p.index, p]));
|
|
221
|
+
const frame = dimensions.height - dimensions.marginTop - dimensions.marginBottom;
|
|
222
|
+
const px = height * frame;
|
|
223
|
+
const X = values2.x;
|
|
224
|
+
const Y = values2.y;
|
|
225
|
+
const texts = g.querySelectorAll("text");
|
|
226
|
+
index.forEach((i, k) => {
|
|
227
|
+
const t = texts[k];
|
|
228
|
+
const p = byIndex.get(k);
|
|
229
|
+
if (!t || !p) return;
|
|
230
|
+
const pos = horizontal ? X?.[i] : Y?.[i];
|
|
231
|
+
const sc = scales[axis];
|
|
232
|
+
const tick = pos ?? (sc ? (sc(labels[k]) ?? Number.NaN) + (sc.bandwidth?.() ?? 0) / 2 : void 0);
|
|
233
|
+
if (tick === void 0 || Number.isNaN(tick)) return;
|
|
234
|
+
const w = px * (p.aspect ?? 1);
|
|
235
|
+
const gap = 3;
|
|
236
|
+
const c = side === "bottom" ? [tick, dimensions.height - dimensions.marginBottom + gap + px / 2] : side === "top" ? [tick, dimensions.marginTop - gap - px / 2] : side === "left" ? [dimensions.marginLeft - gap - w / 2, tick] : [dimensions.width - dimensions.marginRight + gap + w / 2, tick];
|
|
237
|
+
const [cx, cy] = c;
|
|
238
|
+
const img = context.document.createElementNS("http://www.w3.org/2000/svg", "image");
|
|
239
|
+
img.setAttribute("href", p.url);
|
|
240
|
+
img.setAttribute("preserveAspectRatio", "xMidYMid meet");
|
|
241
|
+
img.setAttribute("data-sdv-axis", axis);
|
|
242
|
+
img.setAttribute("data-sdv-tick", String(tick));
|
|
243
|
+
img.setAttribute("aria-label", `${placedName(p, o.league, labels[k])} ${kind}`);
|
|
244
|
+
stampImage(img, p, kind, px, frame, cx, cy);
|
|
245
|
+
t.replaceWith(img);
|
|
246
|
+
});
|
|
247
|
+
return g;
|
|
248
|
+
};
|
|
249
|
+
const margin = Math.round(height * 400) + tickSize + 8;
|
|
250
|
+
const marginKey = `margin${side[0]?.toUpperCase()}${side.slice(1)}`;
|
|
251
|
+
return AXIS[axis]({
|
|
252
|
+
...pass,
|
|
253
|
+
tickFormat: (d) => String(d),
|
|
254
|
+
tickSize,
|
|
255
|
+
label: o.label ?? null,
|
|
256
|
+
...o.anchor ? { anchor: o.anchor } : {},
|
|
257
|
+
[marginKey]: pass[marginKey] ?? pass.margin ?? margin,
|
|
258
|
+
// a caller's own margin on the anchored side wins
|
|
259
|
+
render: compose(outer, render)
|
|
260
|
+
// a caller's render wraps sdvplot's swap
|
|
261
|
+
});
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
// src/plot/scales.ts
|
|
265
|
+
function teamColor(league, o = {}) {
|
|
266
|
+
const naValue = o.naValue ?? "grey";
|
|
267
|
+
const suffix = o.alpha === void 0 ? "" : Math.round(checkAlpha(o.alpha) * 255).toString(16).padStart(2, "0");
|
|
268
|
+
const { domain, colors } = teamColorDomain(league, {
|
|
269
|
+
which: o.which,
|
|
270
|
+
idSystem: o.idSystem,
|
|
271
|
+
strict: o.strict,
|
|
272
|
+
season: o.season,
|
|
273
|
+
values: o.values
|
|
274
|
+
});
|
|
275
|
+
const range = colors.map((c) => c === void 0 ? naValue : hex6(c) + suffix);
|
|
276
|
+
return { type: "ordinal", domain, range, unknown: naValue, ...o.legend ? { legend: true } : {} };
|
|
277
|
+
}
|
|
278
|
+
var teamFill = teamColor;
|
|
279
|
+
function refLines(data, o, reducer) {
|
|
280
|
+
if (o.x === void 0 && o.y === void 0)
|
|
281
|
+
throw new InputError(`${reducer}Lines() needs an x and/or a y channel`);
|
|
282
|
+
const { x: _x, y: _y, ...pass } = o;
|
|
283
|
+
const style = {
|
|
284
|
+
...pass,
|
|
285
|
+
stroke: o.stroke ?? "red",
|
|
286
|
+
strokeDasharray: o.strokeDasharray ?? "4 4",
|
|
287
|
+
strokeWidth: o.strokeWidth ?? 1,
|
|
288
|
+
...o.strokeOpacity !== void 0 ? { strokeOpacity: o.strokeOpacity } : {}
|
|
289
|
+
};
|
|
290
|
+
const d = data;
|
|
291
|
+
const marks = [];
|
|
292
|
+
if (o.x !== void 0)
|
|
293
|
+
marks.push(Plot6.ruleX(d, Plot6.groupZ({ x: reducer }, { x: o.x, ...style })));
|
|
294
|
+
if (o.y !== void 0)
|
|
295
|
+
marks.push(Plot6.ruleY(d, Plot6.groupZ({ y: reducer }, { y: o.y, ...style })));
|
|
296
|
+
return marks;
|
|
297
|
+
}
|
|
298
|
+
function meanLines(data, o) {
|
|
299
|
+
return refLines(data, o, "mean");
|
|
300
|
+
}
|
|
301
|
+
function medianLines(data, o) {
|
|
302
|
+
return refLines(data, o, "median");
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
// src/plot/title.ts
|
|
306
|
+
function titleImage(figure, o) {
|
|
307
|
+
const height = o.height ?? 24;
|
|
308
|
+
if (!(typeof height === "number" && Number.isFinite(height) && height >= 1))
|
|
309
|
+
throw new InputError(`height is a pixel height of at least 1, got ${String(height)}`);
|
|
310
|
+
const side = o.side ?? "left";
|
|
311
|
+
if (side !== "left" && side !== "right")
|
|
312
|
+
throw new InputError(`side must be "left" or "right", got ${String(side)}`);
|
|
313
|
+
const bare = figure.tagName.toLowerCase() === "svg";
|
|
314
|
+
if (bare && !o.title)
|
|
315
|
+
throw new InputError("the plot has no title; pass {title} (or render with Plot.plot({title}))");
|
|
316
|
+
if (!bare && !figure.querySelector("h2"))
|
|
317
|
+
throw new InputError("the figure has no <h2> title; render with Plot.plot({title}) first");
|
|
318
|
+
let src;
|
|
319
|
+
if (o.league) {
|
|
320
|
+
const lo = o.season === void 0 ? {} : { season: o.season };
|
|
321
|
+
src = logoUrlSync(o.image, o.league, lo);
|
|
322
|
+
} else {
|
|
323
|
+
const s = String(o.image);
|
|
324
|
+
if (!/^https:\/\//.test(s))
|
|
325
|
+
throw new InputError(`image must be an https URL when league is not given, got ${s}`);
|
|
326
|
+
src = s;
|
|
327
|
+
}
|
|
328
|
+
const doc = figure.ownerDocument;
|
|
329
|
+
let fig;
|
|
330
|
+
if (bare) {
|
|
331
|
+
fig = doc.createElement("figure");
|
|
332
|
+
const heading = doc.createElement("h2");
|
|
333
|
+
heading.textContent = o.title;
|
|
334
|
+
fig.append(heading, figure);
|
|
335
|
+
} else fig = figure;
|
|
336
|
+
if (src === void 0) return fig;
|
|
337
|
+
const h2 = fig.querySelector("h2");
|
|
338
|
+
const img = doc.createElement("img");
|
|
339
|
+
img.setAttribute("src", src);
|
|
340
|
+
img.setAttribute("height", String(height));
|
|
341
|
+
img.setAttribute("alt", "");
|
|
342
|
+
img.style.verticalAlign = "middle";
|
|
343
|
+
img.style.margin = side === "left" ? "0 0.4em 0 0" : "0 0 0 0.4em";
|
|
344
|
+
if (side === "left") h2.prepend(img);
|
|
345
|
+
else h2.append(img);
|
|
346
|
+
return fig;
|
|
347
|
+
}
|
|
348
|
+
function teamTiers(rows, o) {
|
|
349
|
+
const t = prepareTiers(rows, o.league, o);
|
|
350
|
+
const pts = t.x.map((x, i) => ({
|
|
351
|
+
x,
|
|
352
|
+
y: t.y[i],
|
|
353
|
+
team: t.teamIds[i],
|
|
354
|
+
label: t.labels[i]
|
|
355
|
+
}));
|
|
356
|
+
const marks = [
|
|
357
|
+
Plot6.ruleY(t.lines, { stroke: t.theme.line }),
|
|
358
|
+
o.devel ? Plot6.text(pts, { x: "x", y: "y", text: "label", fill: t.theme.text, fontWeight: "bold" }) : logos(pts, {
|
|
359
|
+
league: o.league,
|
|
360
|
+
x: "x",
|
|
361
|
+
y: "y",
|
|
362
|
+
team: "team",
|
|
363
|
+
idSystem: "team_id",
|
|
364
|
+
height: t.height,
|
|
365
|
+
alpha: t.alpha,
|
|
366
|
+
variant: t.variant,
|
|
367
|
+
...o.tip ? { tip: true, title: "label" } : {}
|
|
368
|
+
})
|
|
369
|
+
];
|
|
370
|
+
return {
|
|
371
|
+
width: o.width ?? 640,
|
|
372
|
+
height: 480,
|
|
373
|
+
marginLeft: 110,
|
|
374
|
+
style: { background: t.theme.bg, color: t.theme.text, fontFamily: "sans-serif" },
|
|
375
|
+
...t.title ? { title: t.title, ariaLabel: t.title } : {},
|
|
376
|
+
// the figure is named by its title
|
|
377
|
+
...t.subtitle ? { subtitle: t.subtitle } : {},
|
|
378
|
+
...t.caption ? { caption: t.caption } : {},
|
|
379
|
+
x: { domain: t.xlim, axis: null },
|
|
380
|
+
y: {
|
|
381
|
+
domain: t.ylim,
|
|
382
|
+
reverse: true,
|
|
383
|
+
ticks: t.breaks,
|
|
384
|
+
tickFormat: (d) => t.breakLabels[t.breaks.indexOf(d)] ?? "",
|
|
385
|
+
tickSize: 0,
|
|
386
|
+
label: null,
|
|
387
|
+
fontWeight: "bold"
|
|
388
|
+
},
|
|
389
|
+
marks
|
|
390
|
+
};
|
|
391
|
+
}
|
|
392
|
+
function surface(league, o = {}) {
|
|
393
|
+
const { centerLogo, ...opts } = o;
|
|
394
|
+
const scene = surfaceScene(league, opts);
|
|
395
|
+
const marks = surfaceMark(scene);
|
|
396
|
+
const { team, season } = o;
|
|
397
|
+
if (centerLogo !== void 0 && centerLogo !== false && team !== void 0 && team !== null) {
|
|
398
|
+
const height = centerLogo === true ? 0.25 : checkHeight(centerLogo);
|
|
399
|
+
marks.push(
|
|
400
|
+
logos([{ x: 0, y: 0, team }], {
|
|
401
|
+
league,
|
|
402
|
+
x: "x",
|
|
403
|
+
y: "y",
|
|
404
|
+
team: "team",
|
|
405
|
+
height,
|
|
406
|
+
...season !== void 0 ? { season } : {}
|
|
407
|
+
})
|
|
408
|
+
);
|
|
409
|
+
}
|
|
410
|
+
return { marks, scales: surfaceScales(scene), scene };
|
|
411
|
+
}
|
|
412
|
+
var stampIds = (id) => stampRender(id);
|
|
413
|
+
function withFormat(tip2, format) {
|
|
414
|
+
if (tip2 === false) return false;
|
|
415
|
+
if (tip2 === true) return { format };
|
|
416
|
+
if (typeof tip2 === "string") return { pointer: tip2, format };
|
|
417
|
+
const own = tip2.format;
|
|
418
|
+
return {
|
|
419
|
+
...tip2,
|
|
420
|
+
format: { ...format, ...typeof own === "object" ? own : own === void 0 ? {} : { title: own } }
|
|
421
|
+
};
|
|
422
|
+
}
|
|
423
|
+
function inRing(x, y, r) {
|
|
424
|
+
let inside = false;
|
|
425
|
+
for (let i = 0, j = r.length - 1; i < r.length; j = i++) {
|
|
426
|
+
const [xi, yi] = r[i];
|
|
427
|
+
const [xj, yj] = r[j];
|
|
428
|
+
if (yi > y !== yj > y && x < (xj - xi) * (y - yi) / (yj - yi) + xi) inside = !inside;
|
|
429
|
+
}
|
|
430
|
+
return inside;
|
|
431
|
+
}
|
|
432
|
+
var ZONE_STEPS = 40;
|
|
433
|
+
function zoneSamples(areas, rings) {
|
|
434
|
+
const xs = rings.flatMap((r) => r.map((p) => p[0]));
|
|
435
|
+
const ys = rings.flatMap((r) => r.map((p) => p[1]));
|
|
436
|
+
const [x0, x1, y0, y1] = [Math.min(...xs), Math.max(...xs), Math.min(...ys), Math.max(...ys)];
|
|
437
|
+
const step = Math.max(x1 - x0, y1 - y0) / ZONE_STEPS;
|
|
438
|
+
const out = { data: [], x: [], y: [] };
|
|
439
|
+
for (let i = 0; x0 + (i + 0.5) * step < x1; i++) {
|
|
440
|
+
for (let j = 0; y0 + (j + 0.5) * step < y1; j++) {
|
|
441
|
+
const [x, y] = [x0 + (i + 0.5) * step, y0 + (j + 0.5) * step];
|
|
442
|
+
const area = areas[rings.findIndex((r) => inRing(x, y, r))];
|
|
443
|
+
if (area === void 0) continue;
|
|
444
|
+
out.data.push(area);
|
|
445
|
+
out.x.push(x);
|
|
446
|
+
out.y.push(y);
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
return out;
|
|
450
|
+
}
|
|
451
|
+
function frameOf(f) {
|
|
452
|
+
const frame = typeof f === "object" ? f : FRAMES[f ?? "nba-legacy"];
|
|
453
|
+
if (frame === void 0) throw new InputError(`unknown frame "${String(f)}"`);
|
|
454
|
+
return frame;
|
|
455
|
+
}
|
|
456
|
+
function toPlot([x, y], f) {
|
|
457
|
+
const px = f.x({ x, y });
|
|
458
|
+
const py = f.y({ x, y });
|
|
459
|
+
if (px === null || py === null) throw new InputError(`frame "${f.description}" dropped point (${x}, ${y})`);
|
|
460
|
+
return [px, py];
|
|
461
|
+
}
|
|
462
|
+
function ring(points, f) {
|
|
463
|
+
const out = points.map((p) => toPlot(p, f));
|
|
464
|
+
const first = out[0];
|
|
465
|
+
if (first !== void 0) out.push([first[0], first[1]]);
|
|
466
|
+
return out;
|
|
467
|
+
}
|
|
468
|
+
var polygon = (coordinates) => ({ type: "Polygon", coordinates: [coordinates] });
|
|
469
|
+
function shotCells(cells, o) {
|
|
470
|
+
const {
|
|
471
|
+
r,
|
|
472
|
+
shape = "hex",
|
|
473
|
+
scale = diffScale(),
|
|
474
|
+
prior = LEAGUE_PRIOR_ATTEMPTS,
|
|
475
|
+
frame,
|
|
476
|
+
dropOutside = true,
|
|
477
|
+
stroke = "var(--sdv-muted, #525252)",
|
|
478
|
+
strokeWidth = 0.85,
|
|
479
|
+
sort = (a, b) => a.attempts - b.attempts,
|
|
480
|
+
tip: tip2,
|
|
481
|
+
channels,
|
|
482
|
+
transform,
|
|
483
|
+
initializer: initializer2,
|
|
484
|
+
render,
|
|
485
|
+
...pass
|
|
486
|
+
} = o;
|
|
487
|
+
const f = frameOf(frame);
|
|
488
|
+
const diff = (h) => h.leagueFgPct === null || h.attempts === 0 ? null : shrunkDiff(h.makes, h.attempts, h.leagueFgPct, prior);
|
|
489
|
+
const size = (i) => typeof r === "number" ? r : r[i] ?? 0;
|
|
490
|
+
const centre = (h) => toPlot([h.x, h.y], f);
|
|
491
|
+
const inFrame = (data, facets, _channels, scales, dimensions) => {
|
|
492
|
+
const { x, y } = scales;
|
|
493
|
+
if (!dropOutside || !x || !y) return { data, facets };
|
|
494
|
+
const { width, height, marginTop, marginRight, marginBottom, marginLeft } = dimensions;
|
|
495
|
+
const keep = (i) => {
|
|
496
|
+
const [cx, cy] = centre(data[i]);
|
|
497
|
+
const px = x(cx);
|
|
498
|
+
const py = y(cy);
|
|
499
|
+
return px >= marginLeft && px <= width - marginRight && py >= marginTop && py <= height - marginBottom;
|
|
500
|
+
};
|
|
501
|
+
return { data, facets: facets.map((I) => I.filter(keep)) };
|
|
502
|
+
};
|
|
503
|
+
const geo2 = {
|
|
504
|
+
// each path's accessible name, in the tip's terms ("147 of 181 made, 81.2%"); a caller's ariaLabel wins
|
|
505
|
+
ariaLabel: (h) => `${h.makes} of ${h.attempts} made${h.fgPct === null ? "" : `, ${(h.fgPct * 100).toFixed(1)}%`}`,
|
|
506
|
+
...pass,
|
|
507
|
+
// each path is stamped by its input row, so a transform that makes new rows throws (sameRows)
|
|
508
|
+
...transform === void 0 ? {} : { transform: sameRows(transform) },
|
|
509
|
+
// r = 0 gives a null geometry, which Plot drops (the default `defined` filter)
|
|
510
|
+
geometry: (h, i) => size(i) > 0 ? polygon(
|
|
511
|
+
ring(
|
|
512
|
+
cellPoints(shape, size(i)).map(([vx, vy]) => [h.x + vx, h.y + vy]),
|
|
513
|
+
f
|
|
514
|
+
)
|
|
515
|
+
) : null,
|
|
516
|
+
fill: (h) => scale(diff(h)),
|
|
517
|
+
stroke,
|
|
518
|
+
strokeWidth,
|
|
519
|
+
sort,
|
|
520
|
+
...tip2 === void 0 || tip2 === null ? {} : {
|
|
521
|
+
tip: withFormat(tip2, { attempts: true, fgPct: ".1%", leagueFgPct: ".1%", diff: "+.1%" }),
|
|
522
|
+
x: (h) => centre(h)[0],
|
|
523
|
+
y: (h) => centre(h)[1]
|
|
524
|
+
},
|
|
525
|
+
channels: {
|
|
526
|
+
attempts: { value: "attempts", label: "Attempts" },
|
|
527
|
+
fgPct: { value: "fgPct", label: "FG%" },
|
|
528
|
+
leagueFgPct: { value: "leagueFgPct", label: "League FG%" },
|
|
529
|
+
diff: { value: diff, label: "vs league (shrunk)" },
|
|
530
|
+
...channels
|
|
531
|
+
},
|
|
532
|
+
render: compose(
|
|
533
|
+
render,
|
|
534
|
+
stampIds((i) => `${cells[i]?.x},${cells[i]?.y}`)
|
|
535
|
+
)
|
|
536
|
+
};
|
|
537
|
+
const framed = Plot6.initializer(geo2, inFrame);
|
|
538
|
+
return Plot6.geo(
|
|
539
|
+
cells,
|
|
540
|
+
initializer2 === void 0 ? framed : Plot6.initializer(framed, sameRows(initializer2))
|
|
541
|
+
);
|
|
542
|
+
}
|
|
543
|
+
function shotZones(areas, o) {
|
|
544
|
+
const {
|
|
545
|
+
fill,
|
|
546
|
+
text: text3,
|
|
547
|
+
frame,
|
|
548
|
+
stats,
|
|
549
|
+
fillOpacity = 0.85,
|
|
550
|
+
tip: tip2,
|
|
551
|
+
channels,
|
|
552
|
+
transform,
|
|
553
|
+
initializer: initializer2,
|
|
554
|
+
render,
|
|
555
|
+
...pass
|
|
556
|
+
} = o;
|
|
557
|
+
const f = frameOf(frame);
|
|
558
|
+
const rings = areas.map((a) => ring(a.points, f));
|
|
559
|
+
const marks = [
|
|
560
|
+
Plot6.geo(areas, {
|
|
561
|
+
// each path's accessible name: its zone, then what its label shows ("Paint (non-RA), 136/326"); a caller's
|
|
562
|
+
// ariaLabel wins
|
|
563
|
+
ariaLabel: (a) => text3 === void 0 ? BASKETBALL_ZONE_LABELS[a.zone] : `${BASKETBALL_ZONE_LABELS[a.zone]}, ${text3(a.zone)}`,
|
|
564
|
+
...pass,
|
|
565
|
+
...transform === void 0 ? {} : { transform: sameRows(transform) },
|
|
566
|
+
...initializer2 === void 0 ? {} : { initializer: sameRows(initializer2) },
|
|
567
|
+
geometry: (_a, i) => polygon(rings[i]),
|
|
568
|
+
fill: (a) => fill(a.zone),
|
|
569
|
+
fillOpacity,
|
|
570
|
+
render: compose(
|
|
571
|
+
render,
|
|
572
|
+
stampIds((i) => areas[i]?.zone ?? "")
|
|
573
|
+
)
|
|
574
|
+
})
|
|
575
|
+
];
|
|
576
|
+
if (text3) {
|
|
577
|
+
const labels = areas.map((a) => {
|
|
578
|
+
const [x, y] = toPlot(a.label, f);
|
|
579
|
+
return { x, y, text: text3(a.zone), vertical: a.vertical };
|
|
580
|
+
});
|
|
581
|
+
marks.push(
|
|
582
|
+
Plot6.text(labels, {
|
|
583
|
+
x: "x",
|
|
584
|
+
y: "y",
|
|
585
|
+
text: "text",
|
|
586
|
+
rotate: (d) => d.vertical ? -90 : 0,
|
|
587
|
+
fill: "currentColor",
|
|
588
|
+
stroke: "var(--sdv-bg, white)",
|
|
589
|
+
strokeWidth: 3,
|
|
590
|
+
strokeLinejoin: "round",
|
|
591
|
+
paintOrder: "stroke",
|
|
592
|
+
ariaHidden: "true",
|
|
593
|
+
// the zone paths' names carry the labels' text, so a screen reader hears each once
|
|
594
|
+
pointerEvents: "none"
|
|
595
|
+
// a label sits inside its zone: a click or hover on it is the zone's
|
|
596
|
+
})
|
|
597
|
+
);
|
|
598
|
+
}
|
|
599
|
+
const t = tip2 === void 0 ? false : withFormat(tip2, { x: false, y: false, zone: true, made: true, fgPct: ".1%" });
|
|
600
|
+
if (t !== false) {
|
|
601
|
+
const { pointer: mode, ...options } = t;
|
|
602
|
+
const pointer2 = mode === "x" ? Plot6.pointerX : mode === "y" ? Plot6.pointerY : Plot6.pointer;
|
|
603
|
+
const at = zoneSamples(areas, rings);
|
|
604
|
+
marks.push(
|
|
605
|
+
Plot6.tip(
|
|
606
|
+
at.data,
|
|
607
|
+
pointer2({
|
|
608
|
+
x: at.x,
|
|
609
|
+
y: at.y,
|
|
610
|
+
channels: {
|
|
611
|
+
zone: { value: (a) => BASKETBALL_ZONE_LABELS[a.zone], label: "Zone" },
|
|
612
|
+
...stats === void 0 ? {} : {
|
|
613
|
+
made: {
|
|
614
|
+
value: (a) => `${stats[a.zone].makes}/${stats[a.zone].attempts}`,
|
|
615
|
+
label: "Made"
|
|
616
|
+
},
|
|
617
|
+
fgPct: { value: (a) => stats[a.zone].fgPct, label: "FG%" }
|
|
618
|
+
},
|
|
619
|
+
...channels
|
|
620
|
+
},
|
|
621
|
+
...options
|
|
622
|
+
})
|
|
623
|
+
)
|
|
624
|
+
);
|
|
625
|
+
}
|
|
626
|
+
return marks;
|
|
627
|
+
}
|
|
628
|
+
var SVG_NS = "http://www.w3.org/2000/svg";
|
|
629
|
+
function shootingSignature(points, o = {}) {
|
|
630
|
+
const curve = o.curve ?? "monotone-x";
|
|
631
|
+
const { maxShare, maxFt, stops, id: gradientId } = signatureGradient(points, o.fill ?? diffScale());
|
|
632
|
+
const half = o.halfWidth ?? ((p, m) => Math.max(p.share / m * 20, 0.75) / 190);
|
|
633
|
+
const marks = [];
|
|
634
|
+
if (o.league ?? true) {
|
|
635
|
+
const leagueLine = (strong) => Plot6.line(points, {
|
|
636
|
+
x: "distance",
|
|
637
|
+
y: (p) => p.leagueFgPct === null || strong && p.fgPct === null ? Number.NaN : p.leagueFgPct,
|
|
638
|
+
curve,
|
|
639
|
+
stroke: "currentColor",
|
|
640
|
+
strokeOpacity: strong ? 1 : 0.4,
|
|
641
|
+
strokeDasharray: "3 3"
|
|
642
|
+
});
|
|
643
|
+
marks.push(leagueLine(false), leagueLine(true));
|
|
644
|
+
}
|
|
645
|
+
const edge = (sign) => (p) => p.fgPct === null ? Number.NaN : p.fgPct + sign * half(p, maxShare);
|
|
646
|
+
marks.push(
|
|
647
|
+
Plot6.areaY(points, {
|
|
648
|
+
x: "distance",
|
|
649
|
+
y1: edge(-1),
|
|
650
|
+
y2: edge(1),
|
|
651
|
+
curve,
|
|
652
|
+
stroke: "currentColor",
|
|
653
|
+
strokeOpacity: 0.2,
|
|
654
|
+
strokeWidth: 0.5,
|
|
655
|
+
ariaLabel: () => SIGNATURE_NAME,
|
|
656
|
+
render(index, scales, values2, dimensions, context, next) {
|
|
657
|
+
const g = next?.(index, scales, values2, dimensions, context) ?? null;
|
|
658
|
+
if (g === null) return null;
|
|
659
|
+
const x = scales.x;
|
|
660
|
+
const [x1, x2] = [String(x(0)), String(x(maxFt))];
|
|
661
|
+
const id = gradientId(x1, x2);
|
|
662
|
+
g.setAttribute("fill", `url(#${id})`);
|
|
663
|
+
const doc = context.document;
|
|
664
|
+
const grad = doc.createElementNS(SVG_NS, "linearGradient");
|
|
665
|
+
grad.setAttribute("id", id);
|
|
666
|
+
grad.setAttribute("gradientUnits", "userSpaceOnUse");
|
|
667
|
+
grad.setAttribute("x1", x1);
|
|
668
|
+
grad.setAttribute("x2", x2);
|
|
669
|
+
grad.setAttribute("y1", "0");
|
|
670
|
+
grad.setAttribute("y2", "0");
|
|
671
|
+
for (const [offset, colour] of stops) {
|
|
672
|
+
const s = doc.createElementNS(SVG_NS, "stop");
|
|
673
|
+
s.setAttribute("offset", `${offset}%`);
|
|
674
|
+
s.setAttribute("stop-color", colour);
|
|
675
|
+
grad.appendChild(s);
|
|
676
|
+
}
|
|
677
|
+
const defs = doc.createElementNS(SVG_NS, "defs");
|
|
678
|
+
defs.appendChild(grad);
|
|
679
|
+
g.insertBefore(defs, g.firstChild);
|
|
680
|
+
return g;
|
|
681
|
+
}
|
|
682
|
+
})
|
|
683
|
+
);
|
|
684
|
+
const t = withFormat(o.tip ?? false, {
|
|
685
|
+
x: (d) => `${d} ft`,
|
|
686
|
+
y: ".1%",
|
|
687
|
+
leagueFgPct: ".1%",
|
|
688
|
+
share: ".1%"
|
|
689
|
+
});
|
|
690
|
+
if (t !== false) {
|
|
691
|
+
marks.push(
|
|
692
|
+
Plot6.tip(
|
|
693
|
+
points,
|
|
694
|
+
Plot6.pointerX({
|
|
695
|
+
x: { value: "distance", label: "Distance" },
|
|
696
|
+
y: { value: (p) => p.fgPct ?? Number.NaN, label: "FG%" },
|
|
697
|
+
channels: {
|
|
698
|
+
leagueFgPct: { value: "leagueFgPct", label: "League FG%" },
|
|
699
|
+
share: { value: "share", label: "Share of shots" }
|
|
700
|
+
},
|
|
701
|
+
...t
|
|
702
|
+
})
|
|
703
|
+
)
|
|
704
|
+
);
|
|
705
|
+
}
|
|
706
|
+
return marks;
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
export { axisLogos, headshots, linkIds, logos, meanLines, medianLines, shootingSignature, shotCells, shotZones, sizeRender, surface, teamColor, teamFill, teamTiers, titleImage, wordmarks };
|
|
710
|
+
//# sourceMappingURL=plot.js.map
|
|
711
|
+
//# sourceMappingURL=plot.js.map
|