@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,155 @@
|
|
|
1
|
+
import { colorList, teamPalette, markPlacements, aspect } from './chunk-UL5M6KDE.js';
|
|
2
|
+
import { checkHeight, checkAlpha } from './chunk-7ULOCCFL.js';
|
|
3
|
+
import { resolveSync } from './chunk-ZAEQXFJM.js';
|
|
4
|
+
import { hex6 } from './chunk-J6R2LEJ7.js';
|
|
5
|
+
import { InputError, warn } from './chunk-E7G7VSLV.js';
|
|
6
|
+
|
|
7
|
+
// src/lightweight-charts.ts
|
|
8
|
+
var TRADINGVIEW_NOTICE = "TradingView Lightweight Charts\u2122\nCopyright (\u0441) 2025 TradingView, Inc. https://www.tradingview.com/";
|
|
9
|
+
function withAttribution(chartOptions, o = {}) {
|
|
10
|
+
const off = o.attributedElsewhere;
|
|
11
|
+
if (off !== void 0 && typeof off !== "boolean")
|
|
12
|
+
throw new InputError(
|
|
13
|
+
`attributedElsewhere is true (your page attributes TradingView itself) or left out, not ${JSON.stringify(off)}`
|
|
14
|
+
);
|
|
15
|
+
const opts = chartOptions ?? {};
|
|
16
|
+
if (off !== true && opts.layout?.attributionLogo === false)
|
|
17
|
+
warn(
|
|
18
|
+
"lightweight-charts:attribution",
|
|
19
|
+
"layout.attributionLogo: false was turned back on: Lightweight Charts' licence asks for TradingView's notice and link. Pass { attributedElsewhere: true } only if your page shows both itself"
|
|
20
|
+
);
|
|
21
|
+
return { ...opts, layout: { ...opts.layout, attributionLogo: off !== true } };
|
|
22
|
+
}
|
|
23
|
+
function attributionNotice(container) {
|
|
24
|
+
if (typeof document === "undefined") return container;
|
|
25
|
+
for (const c of Array.from(container.children))
|
|
26
|
+
if (c instanceof HTMLElement && c.dataset.sdvAttribution === "tradingview") return c;
|
|
27
|
+
const doc = container.ownerDocument;
|
|
28
|
+
const url = "https://www.tradingview.com/";
|
|
29
|
+
const p = doc.createElement("p");
|
|
30
|
+
p.dataset.sdvAttribution = "tradingview";
|
|
31
|
+
p.style.whiteSpace = "pre-line";
|
|
32
|
+
const a = doc.createElement("a");
|
|
33
|
+
a.href = url;
|
|
34
|
+
a.textContent = url;
|
|
35
|
+
p.append(TRADINGVIEW_NOTICE.slice(0, -url.length), a);
|
|
36
|
+
container.append(p);
|
|
37
|
+
return p;
|
|
38
|
+
}
|
|
39
|
+
var UNITS = ["probability", "american"];
|
|
40
|
+
var checkUnits = (u) => {
|
|
41
|
+
if (u === void 0 || UNITS.includes(u)) return u;
|
|
42
|
+
throw new InputError(`units is "probability" or "american", not ${JSON.stringify(u)}`);
|
|
43
|
+
};
|
|
44
|
+
var percent = (p) => `${Number((p * 100).toFixed(1))}%`;
|
|
45
|
+
var american = (v) => {
|
|
46
|
+
const r = Math.sign(v) * Math.round(Math.abs(v));
|
|
47
|
+
return r === 0 ? "0" : `${r > 0 ? "+" : ""}${r}`;
|
|
48
|
+
};
|
|
49
|
+
function teamSeriesOptions(team, o) {
|
|
50
|
+
const units = checkUnits(o.units);
|
|
51
|
+
const teams = o.teams ?? [team];
|
|
52
|
+
const at = teams.indexOf(team);
|
|
53
|
+
if (at < 0) throw new InputError(`team ${JSON.stringify(team)} is not in teams ${JSON.stringify(teams)}`);
|
|
54
|
+
const fallback = hex6(o.fallback ?? "#808080", { dropAlpha: true });
|
|
55
|
+
const season = o.season === void 0 ? {} : { season: o.season };
|
|
56
|
+
const ids = resolveSync(teams, o.league, {
|
|
57
|
+
...season,
|
|
58
|
+
...o.idSystem === void 0 ? {} : { idSystem: o.idSystem }
|
|
59
|
+
});
|
|
60
|
+
const id = ids[at];
|
|
61
|
+
let color = fallback;
|
|
62
|
+
if (id !== void 0 && id !== null) {
|
|
63
|
+
const known = [...new Set(ids.filter((x) => x !== void 0 && x !== null))];
|
|
64
|
+
const which = o.which ?? "primary";
|
|
65
|
+
const co = { ...season, idSystem: "team_id" };
|
|
66
|
+
const main = colorList(o.league, known, { ...co, which });
|
|
67
|
+
const other = colorList(o.league, known, { ...co, which: which === "primary" ? "secondary" : "primary" });
|
|
68
|
+
let colors = main.map((c, i) => hex6(c ?? other[i] ?? fallback, { dropAlpha: true }));
|
|
69
|
+
if (o.background !== void 0) colors = teamPalette(colors, other, hex6(o.background), known);
|
|
70
|
+
color = colors[known.indexOf(id)];
|
|
71
|
+
}
|
|
72
|
+
if (units === void 0) return { color };
|
|
73
|
+
return {
|
|
74
|
+
color,
|
|
75
|
+
priceFormat: units === "probability" ? { type: "custom", formatter: percent, minMove: 1e-3 } : { type: "custom", formatter: american, minMove: 1 }
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
var drawn = /* @__PURE__ */ new WeakMap();
|
|
79
|
+
function logoMarkers(rows, o) {
|
|
80
|
+
const h = checkHeight(o.height ?? 0.1);
|
|
81
|
+
const a = checkAlpha(o.alpha ?? 1);
|
|
82
|
+
const units = checkUnits(o.units);
|
|
83
|
+
if (units !== void 0)
|
|
84
|
+
for (const r of rows) {
|
|
85
|
+
if (r[o.y] === void 0 || r[o.y] === null) continue;
|
|
86
|
+
const v = Number(r[o.y]);
|
|
87
|
+
if (units === "probability" && !(v >= 0 && v <= 1))
|
|
88
|
+
throw new InputError(
|
|
89
|
+
`a probability is a fraction in [0, 1], not ${r[o.y]} (a percent? divide it by 100)`
|
|
90
|
+
);
|
|
91
|
+
if (units === "american" && !(Math.abs(v) >= 100))
|
|
92
|
+
throw new InputError(`American odds are -100 or less or +100 or more, not ${r[o.y]} (decimal odds?)`);
|
|
93
|
+
}
|
|
94
|
+
const ps = markPlacements(rows, "logo", o);
|
|
95
|
+
const images = /* @__PURE__ */ new Map();
|
|
96
|
+
let at;
|
|
97
|
+
const load = (url) => {
|
|
98
|
+
if (images.has(url) || typeof Image === "undefined") return;
|
|
99
|
+
const img = new Image();
|
|
100
|
+
img.crossOrigin = "anonymous";
|
|
101
|
+
images.set(url, img);
|
|
102
|
+
img.onload = () => at?.requestUpdate();
|
|
103
|
+
img.onerror = () => {
|
|
104
|
+
images.set(url, null);
|
|
105
|
+
warn(`lightweight-charts:image:${url}`, `could not load ${url}, so it is not drawn`);
|
|
106
|
+
};
|
|
107
|
+
img.src = url;
|
|
108
|
+
};
|
|
109
|
+
const draw = (target) => {
|
|
110
|
+
const c = at;
|
|
111
|
+
if (c === void 0) return;
|
|
112
|
+
target.useMediaCoordinateSpace(({ context: ctx, mediaSize }) => {
|
|
113
|
+
const out = [];
|
|
114
|
+
const ph = h * mediaSize.height;
|
|
115
|
+
ctx.save();
|
|
116
|
+
ctx.globalAlpha = a;
|
|
117
|
+
for (const p of ps) {
|
|
118
|
+
const img = images.get(p.url);
|
|
119
|
+
if (!img?.complete || img.naturalWidth === 0) continue;
|
|
120
|
+
const cx = c.chart.timeScale().timeToCoordinate(p.x);
|
|
121
|
+
const cy = c.series.priceToCoordinate(Number(p.y));
|
|
122
|
+
if (cx === null || cy === null) continue;
|
|
123
|
+
if (!(cx >= 0 && cx <= mediaSize.width && cy >= 0 && cy <= mediaSize.height)) continue;
|
|
124
|
+
const pw = ph * aspect(p);
|
|
125
|
+
ctx.drawImage(img, cx - pw / 2, cy - ph / 2, pw, ph);
|
|
126
|
+
out.push([p.id, p.x, p.y, h, p.url]);
|
|
127
|
+
}
|
|
128
|
+
ctx.restore();
|
|
129
|
+
drawn.set(primitive, out);
|
|
130
|
+
});
|
|
131
|
+
};
|
|
132
|
+
const views = [{ renderer: () => ({ draw }) }];
|
|
133
|
+
const primitive = {
|
|
134
|
+
attached(param) {
|
|
135
|
+
at = param;
|
|
136
|
+
for (const p of ps) load(p.url);
|
|
137
|
+
param.requestUpdate();
|
|
138
|
+
},
|
|
139
|
+
detached() {
|
|
140
|
+
at = void 0;
|
|
141
|
+
drawn.delete(primitive);
|
|
142
|
+
},
|
|
143
|
+
updateAllViews() {
|
|
144
|
+
},
|
|
145
|
+
paneViews: () => views
|
|
146
|
+
};
|
|
147
|
+
return primitive;
|
|
148
|
+
}
|
|
149
|
+
function drawnMarks(primitive) {
|
|
150
|
+
return [...drawn.get(primitive) ?? []];
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
export { TRADINGVIEW_NOTICE, attributionNotice, drawnMarks, logoMarkers, teamSeriesOptions, withAttribution };
|
|
154
|
+
//# sourceMappingURL=lightweight-charts.js.map
|
|
155
|
+
//# sourceMappingURL=lightweight-charts.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/lightweight-charts.ts"],"names":[],"mappings":"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","file":"lightweight-charts.js","sourcesContent":["/**\n * The TradingView Lightweight Charts adapter: chart options that keep TradingView's attribution on, team-coloured\n * series options with prices read as probabilities or odds, and team logos drawn at points by a series primitive.\n * Pure: lightweight-charts is never imported (the types here are structural, read from lightweight-charts 5.2.1), so\n * everything can be built anywhere, a server included; only `createChart(...)` needs a browser. Call after\n * `loadLeague(league)` / `preloadAll()`.\n *\n * Lightweight Charts is Apache-2.0 and asks every page that shows it for TradingView's attribution notice\n * (`TRADINGVIEW_NOTICE`) and a link to https://www.tradingview.com/. `withAttribution` keeps the library's own\n * attribution logo on, which is that link, and `attributionNotice` puts the notice on the page.\n */\nimport {\n type MarkOptions,\n type Row,\n aspect,\n checkAlpha,\n checkHeight,\n colorList,\n markPlacements,\n} from \"./_web.js\";\nimport type { DrawnMark } from \"./_web.js\";\nimport { hex6 } from \"./contrast.js\";\nimport { InputError, warn } from \"./errors.js\";\nimport { resolveSync } from \"./resolve.js\";\nimport type { Value } from \"./resolve.js\";\nimport { teamPalette } from \"./team-palette.js\";\nimport type { IdSystem, League, SeasonInput, Which } from \"./types.js\";\nexport type { DrawnMark, MarkOptions, Row } from \"./_web.js\";\n\n/**\n * TradingView's attribution notice: the NOTICE file of tradingview/lightweight-charts at v5.2.1, verbatim (its \"с\" is\n * the Cyrillic letter, as upstream writes it). Lightweight Charts' licence asks for it, and a link to\n * https://www.tradingview.com/, on the page that shows the chart.\n */\nexport const TRADINGVIEW_NOTICE =\n \"TradingView Lightweight Charts™\\nCopyright (с) 2025 TradingView, Inc. https://www.tradingview.com/\";\n\n/** `withAttribution` options. */\nexport interface AttributionOptions {\n /**\n * `true` turns TradingView's attribution logo off. Pass it only when the page already shows TradingView's\n * attribution notice and a link to https://www.tradingview.com/ elsewhere: meeting Lightweight Charts' licence is\n * then on you. Anything but `true` (or `false`, the default) throws.\n */\n attributedElsewhere?: boolean;\n}\n\n/**\n * Chart options for `createChart` with TradingView's attribution logo on (`layout.attributionLogo: true`), the\n * library's own link to https://www.tradingview.com/ that its licence asks for. The options are copied, never changed;\n * the rest of them is kept. A `layout.attributionLogo: false` in them is turned back on, with one warning: only the\n * explicit `{ attributedElsewhere: true }` turns it off, and the licence is then the caller's to meet.\n *\n * @example\n * ```ts\n * import { attributionNotice, withAttribution } from \"@sportsdataverse/sdvplot/lightweight-charts\";\n * import { createChart } from \"lightweight-charts\";\n *\n * const el = document.body.appendChild(document.createElement(\"div\"));\n * // the chart, with TradingView's logo (its link) on it, and TradingView's notice on the page\n * const chart = createChart(el, withAttribution({ width: 640, height: 320 }));\n * const notice = attributionNotice(el.parentElement ?? el);\n * [chart.options().layout.attributionLogo, notice.textContent];\n * ```\n */\nexport function withAttribution<F extends object = Record<never, never>>(\n chartOptions?: F,\n o: AttributionOptions = {},\n): F & { layout: { attributionLogo: boolean } } {\n const off = o.attributedElsewhere;\n if (off !== undefined && typeof off !== \"boolean\")\n throw new InputError(\n `attributedElsewhere is true (your page attributes TradingView itself) or left out, not ${JSON.stringify(off)}`,\n );\n const opts = (chartOptions ?? {}) as F & { layout?: { attributionLogo?: unknown } };\n if (off !== true && opts.layout?.attributionLogo === false)\n warn(\n \"lightweight-charts:attribution\",\n \"layout.attributionLogo: false was turned back on: Lightweight Charts' licence asks for TradingView's notice and link. Pass { attributedElsewhere: true } only if your page shows both itself\",\n );\n return { ...opts, layout: { ...opts.layout, attributionLogo: off !== true } };\n}\n\n/**\n * Puts TradingView's attribution notice on the page, as Lightweight Charts' licence asks: a `<p>` appended to\n * `container`, holding `TRADINGVIEW_NOTICE` (its URL a link, its two lines kept), and returned. Idempotent: called\n * again on the same container it returns the notice already there rather than adding a second. Give it the element\n * around the chart (`el.parentElement ?? el` for the chart's own `el`), so the notice sits under the chart. On a\n * server (no `document`) it does nothing and returns `container`. `withAttribution`'s example draws a chart with it.\n *\n * @example\n * ```ts\n * import { TRADINGVIEW_NOTICE, attributionNotice } from \"@sportsdataverse/sdvplot/lightweight-charts\";\n *\n * const figure = document.body.appendChild(document.createElement(\"figure\"));\n * const notice = attributionNotice(figure);\n * // once per container, and exactly TradingView's notice\n * [attributionNotice(figure) === notice, notice.textContent === TRADINGVIEW_NOTICE];\n * ```\n */\nexport function attributionNotice(container: HTMLElement): HTMLElement {\n if (typeof document === \"undefined\") return container;\n for (const c of Array.from(container.children))\n if (c instanceof HTMLElement && c.dataset.sdvAttribution === \"tradingview\") return c;\n const doc = container.ownerDocument;\n const url = \"https://www.tradingview.com/\";\n const p = doc.createElement(\"p\");\n p.dataset.sdvAttribution = \"tradingview\";\n p.style.whiteSpace = \"pre-line\"; // the notice's two lines\n const a = doc.createElement(\"a\");\n a.href = url;\n a.textContent = url;\n p.append(TRADINGVIEW_NOTICE.slice(0, -url.length), a);\n container.append(p);\n return p;\n}\n\n/** What a price is: a probability (a fraction in [0, 1], read as a percentage) or American odds (-110, +150). */\nexport type PriceUnits = \"probability\" | \"american\";\nconst UNITS: readonly PriceUnits[] = [\"probability\", \"american\"];\nconst checkUnits = (u: unknown): PriceUnits | undefined => {\n if (u === undefined || UNITS.includes(u as PriceUnits)) return u as PriceUnits | undefined;\n throw new InputError(`units is \"probability\" or \"american\", not ${JSON.stringify(u)}`);\n};\nconst percent = (p: number): string => `${Number((p * 100).toFixed(1))}%`;\n/** American odds as text: rounded half away from zero, signed, and \"0\" (never \"+0\" or \"-0\") for a zero. */\nconst american = (v: number): string => {\n const r = Math.sign(v) * Math.round(Math.abs(v));\n return r === 0 ? \"0\" : `${r > 0 ? \"+\" : \"\"}${r}`;\n};\n\n/** A Lightweight Charts custom price format. */\nexport interface LwcPriceFormat {\n /** Always \"custom\": the formatter reads the prices. */\n type: \"custom\";\n /** A price as text: \"58.5%\" for a probability, \"+150\" for American odds. */\n formatter: (price: number) => string;\n /** The smallest price step: 0.001 for a probability (0.1%), 1 for American odds. */\n minMove: number;\n}\n/** What `teamSeriesOptions` returns: a LineSeries' options. */\nexport interface LwcTeamSeriesOptions {\n /** The line's colour, `#rrggbb`. */\n color: string;\n /** With `units`: how the price scale, the crosshair label and the last-value label read the prices. */\n priceFormat?: LwcPriceFormat;\n}\n/** `teamSeriesOptions` options. */\nexport interface TeamSeriesOptions {\n /** The league the team belongs to, e.g. \"nfl\". */\n league: League;\n /** Every team on the chart, in order (the team itself among them), so that with `background` each gets its own\n * colour. Default: the team alone. */\n teams?: readonly Value[];\n /** What the prices are: \"probability\" (a fraction in [0, 1], read \"58.5%\") or \"american\" (odds, read \"+150\").\n * Default: Lightweight Charts' own number format. */\n units?: PriceUnits;\n /** The season the team is resolved (and coloured) for. */\n season?: SeasonInput;\n /** \"primary\" (default) or \"secondary\". */\n which?: Which;\n /** How the team values are read. Default \"auto\". */\n idSystem?: IdSystem;\n /** The colour of a team that does not resolve. Default \"#808080\". */\n fallback?: string;\n /**\n * The chart's background. Given, every colour keeps 3:1 contrast against it (dark mode): a team colour under that\n * is swapped for the team's other colour, else moved towards white (on a dark background) or black (a light one)\n * until it reads. A colour that would then sit too close to an earlier team's in `teams` (CIE76 ΔE under 20) takes\n * the team's other colour, or the nearest colour to its own (lightness, hue or chroma stepped) that is distinct and\n * still reads; failing that it is kept, with one warning.\n */\n background?: string;\n}\n\n/**\n * A LineSeries' options in a team's colour, for `chart.addSeries(LineSeries, ...)`. With `units`, the prices read as\n * probabilities (\"58.5%\") or American odds (\"+150\", \"-120\") on the price scale, the crosshair and the last-value line.\n * With `background`, the colour keeps 3:1 contrast against it and aims to stay distinct from the other `teams` on the\n * chart (when it cannot, it keeps the readable colour and warns once).\n * A team that does not resolve takes `fallback` and warns; it never moves a real team's colour.\n *\n * @example\n * ```ts\n * import { loadLeague } from \"@sportsdataverse/sdvplot\";\n * import { teamSeriesOptions } from \"@sportsdataverse/sdvplot/lightweight-charts\";\n *\n * await loadLeague(\"nfl\");\n * const teams = [\"PHI\", \"KC\"];\n * const phi = teamSeriesOptions(\"PHI\", { league: \"nfl\", teams, units: \"probability\", background: \"#181a1b\" });\n * // in the browser: chart.addSeries(LineSeries, phi)\n * [phi.color, phi.priceFormat?.formatter(0.5846)];\n * ```\n */\nexport function teamSeriesOptions(team: Value, o: TeamSeriesOptions): LwcTeamSeriesOptions {\n const units = checkUnits(o.units);\n const teams = o.teams ?? [team];\n const at = teams.indexOf(team);\n if (at < 0) throw new InputError(`team ${JSON.stringify(team)} is not in teams ${JSON.stringify(teams)}`);\n const fallback = hex6(o.fallback ?? \"#808080\", { dropAlpha: true });\n const season = o.season === undefined ? {} : { season: o.season };\n const ids = resolveSync(teams, o.league, {\n ...season,\n ...(o.idSystem === undefined ? {} : { idSystem: o.idSystem }),\n });\n const id = ids[at];\n let color = fallback;\n if (id !== undefined && id !== null) {\n // the palette runs over the resolved teams only: an unresolved one never moves a real team's colour\n const known = [...new Set(ids.filter((x): x is NonNullable<typeof x> => x !== undefined && x !== null))];\n const which = o.which ?? \"primary\";\n const co = { ...season, idSystem: \"team_id\" as const };\n const main = colorList(o.league, known, { ...co, which });\n const other = colorList(o.league, known, { ...co, which: which === \"primary\" ? \"secondary\" : \"primary\" });\n let colors = main.map((c, i) => hex6(c ?? other[i] ?? fallback, { dropAlpha: true }));\n if (o.background !== undefined) colors = teamPalette(colors, other, hex6(o.background), known);\n color = colors[known.indexOf(id)] as string;\n }\n if (units === undefined) return { color };\n return {\n color,\n priceFormat:\n units === \"probability\"\n ? { type: \"custom\", formatter: percent, minMove: 0.001 }\n : { type: \"custom\", formatter: american, minMove: 1 },\n };\n}\n\n/** A Lightweight Charts time: a UTC timestamp in seconds, a \"yyyy-mm-dd\" string or a business day. */\nexport type LwcTime = number | string | { year: number; month: number; day: number };\n/** The parts of what Lightweight Charts attaches a series primitive with that `logoMarkers` reads. */\nexport interface LwcAttached {\n /** The chart: its time scale places `x`. */\n chart: { timeScale(): { timeToCoordinate(time: LwcTime): number | null } };\n /** The series the primitive is attached to: its price scale places `y`. */\n series: { priceToCoordinate(price: number): number | null };\n /** Asks the chart to redraw. */\n requestUpdate: () => void;\n}\n/** The drawing target Lightweight Charts hands a primitive's renderer (fancy-canvas' `CanvasRenderingTarget2D`). */\nexport interface LwcRenderTarget {\n /** Calls `f` with the 2D context scaled to CSS pixels and the pane's size in them. */\n useMediaCoordinateSpace<T>(\n f: (scope: { context: CanvasRenderingContext2D; mediaSize: { width: number; height: number } }) => T,\n ): T;\n}\n/** A Lightweight Charts series primitive: attach it with `series.attachPrimitive(primitive)`. */\nexport interface LwcSeriesPrimitive {\n /** Lightweight Charts calls this on `attachPrimitive`: the logos start loading. */\n attached(param: LwcAttached): void;\n /** Lightweight Charts calls this on `detachPrimitive`: the chart is forgotten, nothing more is drawn, and\n * `drawnMarks` reads empty. */\n detached(): void;\n /** Lightweight Charts calls this when the view changes; the logos are placed at each draw, so it does nothing. */\n updateAllViews(): void;\n /** The one pane view, whose renderer paints the logos (the same array every call, as the library asks). */\n paneViews(): readonly { renderer(): { draw(target: LwcRenderTarget): void } | null }[];\n}\n/** `logoMarkers` options: the spec adapters' mark options, less `embed`, plus what the prices are. */\nexport interface LogoMarkersOptions extends Omit<MarkOptions, \"embed\"> {\n /** What `y` is: \"probability\" values must be fractions in [0, 1], \"american\" values -100 or less or +100 or more. */\n units?: PriceUnits;\n}\n\n// ponytail: a WeakMap per primitive of what it last drew, so drawnMarks reads what reached the canvas\nconst drawn = new WeakMap<LwcSeriesPrimitive, DrawnMark[]>();\n\n/**\n * Team logos on a Lightweight Charts series: a series primitive (`series.attachPrimitive(...)`) whose renderer paints\n * each row's logo centred on its `x` (a time, as in the series' data) and `y` (a price on the series' scale), placed\n * with the chart's own `timeToCoordinate` / `priceToCoordinate`, `height` of the pane tall (a fraction, as in the spec\n * adapters). Lightweight Charts' series markers draw only shapes and text, so logos take a primitive. The images start\n * loading when it is attached, and the chart redraws as each one lands; building it fetches nothing. A team with no\n * logo draws nothing and warns once, as `sdvplot/echarts` does; an image that fails to load is never drawn and warns\n * once; a row at a time the chart has no point for, or off the pane, is not drawn.\n *\n * @example\n * ```ts\n * import { loadLeague } from \"@sportsdataverse/sdvplot\";\n * import { logoMarkers } from \"@sportsdataverse/sdvplot/lightweight-charts\";\n *\n * await loadLeague(\"nfl\");\n * // Super Bowl LIX's final reading (ESPN): 3600 seconds played, Philadelphia's win probability 1\n * const rows = [{ t: 3600, wp: 1, team: \"PHI\" }];\n * const logos = logoMarkers(rows, { x: \"t\", y: \"wp\", team: \"team\", league: \"nfl\", units: \"probability\" });\n * // in the browser: series.attachPrimitive(logos)\n * typeof logos.paneViews;\n * ```\n */\nexport function logoMarkers(rows: readonly Row[], o: LogoMarkersOptions): LwcSeriesPrimitive {\n const h = checkHeight(o.height ?? 0.1);\n const a = checkAlpha(o.alpha ?? 1);\n const units = checkUnits(o.units);\n if (units !== undefined)\n for (const r of rows) {\n // a missing y is left to markPlacements, which skips the row with one warning (Number(null) is 0, a \"probability\")\n if (r[o.y] === undefined || r[o.y] === null) continue;\n const v = Number(r[o.y]);\n if (units === \"probability\" && !(v >= 0 && v <= 1))\n throw new InputError(\n `a probability is a fraction in [0, 1], not ${r[o.y]} (a percent? divide it by 100)`,\n );\n if (units === \"american\" && !(Math.abs(v) >= 100))\n throw new InputError(`American odds are -100 or less or +100 or more, not ${r[o.y]} (decimal odds?)`);\n }\n const ps = markPlacements(rows, \"logo\", o); // validates the rows (and warns on a team with no logo) now\n const images = new Map<string, HTMLImageElement | null>(); // null: it failed to load\n let at: LwcAttached | undefined;\n const load = (url: string): void => {\n if (images.has(url) || typeof Image === \"undefined\") return;\n const img = new Image();\n img.crossOrigin = \"anonymous\"; // keeps the canvas exportable\n images.set(url, img);\n img.onload = () => at?.requestUpdate();\n img.onerror = () => {\n images.set(url, null);\n warn(`lightweight-charts:image:${url}`, `could not load ${url}, so it is not drawn`);\n };\n img.src = url;\n };\n const draw = (target: LwcRenderTarget): void => {\n const c = at;\n if (c === undefined) return;\n target.useMediaCoordinateSpace(({ context: ctx, mediaSize }) => {\n const out: DrawnMark[] = [];\n const ph = h * mediaSize.height;\n ctx.save();\n ctx.globalAlpha = a;\n for (const p of ps) {\n const img = images.get(p.url);\n if (!img?.complete || img.naturalWidth === 0) continue;\n const cx = c.chart.timeScale().timeToCoordinate(p.x as LwcTime);\n const cy = c.series.priceToCoordinate(Number(p.y));\n if (cx === null || cy === null) continue;\n if (!(cx >= 0 && cx <= mediaSize.width && cy >= 0 && cy <= mediaSize.height)) continue; // also drops NaN\n const pw = ph * aspect(p);\n ctx.drawImage(img, cx - pw / 2, cy - ph / 2, pw, ph);\n out.push([p.id, p.x, p.y, h, p.url]);\n }\n ctx.restore();\n drawn.set(primitive, out);\n });\n };\n const views = [{ renderer: () => ({ draw }) }] as const; // the same array every time, as the library asks\n const primitive: LwcSeriesPrimitive = {\n attached(param) {\n at = param;\n for (const p of ps) load(p.url);\n param.requestUpdate(); // logos loaded before (attached again) draw now, not at the next unrelated redraw\n },\n detached() {\n at = undefined;\n drawn.delete(primitive);\n },\n updateAllViews() {},\n paneViews: () => views,\n };\n return primitive;\n}\n\n/** Test hook: [teamId, x, y, height, url] per logo the primitive last drew, `height` a fraction of the pane. */\nexport function drawnMarks(primitive: LwcSeriesPrimitive): DrawnMark[] {\n return [...(drawn.get(primitive) ?? [])];\n}\n"]}
|
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
import { GeoJSONSourceSpecification, Map, SymbolLayerSpecification, LineLayerSpecification } from 'maplibre-gl';
|
|
2
|
+
import { a as DrawnMark, R as Row } from './_web-D7Aa5Rd8.js';
|
|
3
|
+
import { L as League, S as SeasonInput, V as Variant, I as IdSystem, W as Which } from './types-C7n6gJF3.js';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* The MapLibre GL JS 6 adapter: team logos as a native `symbol` layer (`logoSymbols`, with `addLogoImages` to load
|
|
7
|
+
* each logo once into the map's images), team-coloured travel lines as a `line` layer (`routeLines`), and a free,
|
|
8
|
+
* keyless basemap (`basemap`, OpenFreeMap). Each builder returns plain style JSON: a GeoJSON source and a layer that
|
|
9
|
+
* reads it, for `map.addSource(set.id, set.source)` and `map.addLayer(set.layer)`. Pure: `maplibre-gl` (an optional
|
|
10
|
+
* peer, 6.14 or later) is never imported at runtime, only its types; the map is passed in. For deck.gl layers on a
|
|
11
|
+
* MapLibre map, hand `@sportsdataverse/sdvplot/deckgl`'s `logoIconLayer` / `passArcLayer` props to deck.gl's layers
|
|
12
|
+
* inside `@deck.gl/mapbox`'s `MapboxOverlay`. Synchronous builders: call after `loadLeague(league)` / `preloadAll()`.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
/** `logoSymbols` options. */
|
|
16
|
+
interface LogoSymbolOptions {
|
|
17
|
+
/** Name of the row column holding each logo's longitude. */
|
|
18
|
+
x: string;
|
|
19
|
+
/** Name of the row column holding each logo's latitude. */
|
|
20
|
+
y: string;
|
|
21
|
+
/** Name of the row column holding the team (abbreviation, name or id, a number or a string; see `idSystem`). */
|
|
22
|
+
team: string;
|
|
23
|
+
/** The league the teams belong to, e.g. "nfl". */
|
|
24
|
+
league: League;
|
|
25
|
+
/** A season (number or string) for every row, or the name of a row column holding each row's season. */
|
|
26
|
+
season?: SeasonInput;
|
|
27
|
+
/** Logo variant, e.g. "dark". Default "default". */
|
|
28
|
+
variant?: Variant;
|
|
29
|
+
/** How the `team` values are read. Default "auto". */
|
|
30
|
+
idSystem?: IdSystem;
|
|
31
|
+
/** The logo's height on screen in px, 1 or more. Default 32. */
|
|
32
|
+
size?: number;
|
|
33
|
+
/** The logos' opacity in [0, 1] (`icon-opacity`). Default 1. */
|
|
34
|
+
alpha?: number;
|
|
35
|
+
/** The source and layer id. Default "sdvplot-logos". */
|
|
36
|
+
id?: string;
|
|
37
|
+
}
|
|
38
|
+
/** A logo feature's properties. */
|
|
39
|
+
interface LogoProperties {
|
|
40
|
+
/** The resolved team id. */
|
|
41
|
+
team: string;
|
|
42
|
+
/** The map image the symbol draws (`icon-image`), one per logo and size. */
|
|
43
|
+
icon: string;
|
|
44
|
+
/** The logo's URL. */
|
|
45
|
+
url: string;
|
|
46
|
+
/** The row's position in the input (left-out rows leave gaps). */
|
|
47
|
+
index: number;
|
|
48
|
+
}
|
|
49
|
+
/** One logo the map needs as an image: `addLogoImages` loads `url` and adds it as `id`, drawn `height` px tall. */
|
|
50
|
+
interface LogoImage {
|
|
51
|
+
id: string;
|
|
52
|
+
url: string;
|
|
53
|
+
height: number;
|
|
54
|
+
}
|
|
55
|
+
/** A GeoJSON point: [longitude, latitude]. */
|
|
56
|
+
interface PointGeometry {
|
|
57
|
+
type: "Point";
|
|
58
|
+
coordinates: [lon: number, lat: number];
|
|
59
|
+
}
|
|
60
|
+
/** A GeoJSON line through [longitude, latitude] positions. */
|
|
61
|
+
interface LineGeometry {
|
|
62
|
+
type: "LineString";
|
|
63
|
+
coordinates: [lon: number, lat: number][];
|
|
64
|
+
}
|
|
65
|
+
/** A GeoJSON feature; its id is the row's index in the input. */
|
|
66
|
+
interface Feature<G, P> {
|
|
67
|
+
type: "Feature";
|
|
68
|
+
id: number;
|
|
69
|
+
geometry: G;
|
|
70
|
+
properties: P;
|
|
71
|
+
}
|
|
72
|
+
/** A GeoJSON source holding a feature collection: `map.addSource(id, source)`. */
|
|
73
|
+
type FeatureSource<G, P> = GeoJSONSourceSpecification & {
|
|
74
|
+
type: "geojson";
|
|
75
|
+
data: {
|
|
76
|
+
type: "FeatureCollection";
|
|
77
|
+
features: Feature<G, P>[];
|
|
78
|
+
};
|
|
79
|
+
};
|
|
80
|
+
/** `logoSymbols`' result: a source and a symbol layer for the map, and the images that layer draws. */
|
|
81
|
+
interface LogoSymbols {
|
|
82
|
+
/** The source id (the layer's own id too). */
|
|
83
|
+
id: string;
|
|
84
|
+
/** One point feature per drawn row; the feature id is the row's index. */
|
|
85
|
+
source: FeatureSource<PointGeometry, LogoProperties>;
|
|
86
|
+
layer: SymbolLayerSpecification;
|
|
87
|
+
/** One per distinct logo: what `addLogoImages` loads. */
|
|
88
|
+
images: LogoImage[];
|
|
89
|
+
}
|
|
90
|
+
/** `routeLines` options. */
|
|
91
|
+
interface RouteLineOptions {
|
|
92
|
+
/** Name of the row column holding each leg's start longitude. */
|
|
93
|
+
x: string;
|
|
94
|
+
/** Name of the row column holding each leg's start latitude. */
|
|
95
|
+
y: string;
|
|
96
|
+
/** Name of the row column holding each leg's end longitude. */
|
|
97
|
+
xend: string;
|
|
98
|
+
/** Name of the row column holding each leg's end latitude. */
|
|
99
|
+
yend: string;
|
|
100
|
+
/** Name of the row column holding the team the leg is coloured by. */
|
|
101
|
+
team: string;
|
|
102
|
+
/** The league the teams belong to, e.g. "nfl". */
|
|
103
|
+
league: League;
|
|
104
|
+
/** The season the teams are resolved for. */
|
|
105
|
+
season?: SeasonInput;
|
|
106
|
+
/** How the `team` values are read. Default "auto". */
|
|
107
|
+
idSystem?: IdSystem;
|
|
108
|
+
/** "primary" (default) or "secondary" colour. */
|
|
109
|
+
which?: Which;
|
|
110
|
+
/**
|
|
111
|
+
* The basemap's background (`basemap("dark").background` under the dark basemap). Given, every team colour keeps 3:1
|
|
112
|
+
* contrast against it and aims to stay distinct from the other teams' (CIE76 ΔE of 20 or more), through the shared
|
|
113
|
+
* team palette, as `teamColorPalette` does in the other adapters.
|
|
114
|
+
*/
|
|
115
|
+
background?: string;
|
|
116
|
+
/** The colour of a team that does not resolve, or has no colour. Default "#808080". */
|
|
117
|
+
fallback?: string;
|
|
118
|
+
/** The line's width in px, 1 or more. Default 3. */
|
|
119
|
+
width?: number;
|
|
120
|
+
/** The lines' opacity in [0, 1] (`line-opacity`). Default 1. */
|
|
121
|
+
alpha?: number;
|
|
122
|
+
/** The source and layer id. Default "sdvplot-routes". */
|
|
123
|
+
id?: string;
|
|
124
|
+
}
|
|
125
|
+
/** A route feature's properties. */
|
|
126
|
+
interface RouteProperties {
|
|
127
|
+
/** The resolved team id, or the row's own value as text when it does not resolve. */
|
|
128
|
+
team: string;
|
|
129
|
+
/** The line's colour, `#rrggbb`. */
|
|
130
|
+
color: string;
|
|
131
|
+
/** The row's position in the input (left-out rows leave gaps). */
|
|
132
|
+
index: number;
|
|
133
|
+
}
|
|
134
|
+
/** `routeLines`' result: a source and a line layer for the map. */
|
|
135
|
+
interface RouteLines {
|
|
136
|
+
id: string;
|
|
137
|
+
/** One two-point line per drawn row; the feature id is the row's index. */
|
|
138
|
+
source: FeatureSource<LineGeometry, RouteProperties>;
|
|
139
|
+
layer: LineLayerSpecification;
|
|
140
|
+
}
|
|
141
|
+
/** The map methods `addLogoImages` calls: a MapLibre `Map`. */
|
|
142
|
+
type LogoImageMap = Pick<Map, "loadImage" | "addImage" | "hasImage">;
|
|
143
|
+
/**
|
|
144
|
+
* Each row's team logo at its (longitude, latitude), as a GeoJSON source and a MapLibre `symbol` layer: one point
|
|
145
|
+
* feature per row (its `id` is the row's index; `team`, `icon`, `url` and `index` are its properties), and a layer
|
|
146
|
+
* drawing `icon-image: ["get", "icon"]`, every logo shown even where two overlap. A team with no logo (or one that
|
|
147
|
+
* does not resolve) is left out with one warning; so is a row with a missing, non-numeric or infinite coordinate (a
|
|
148
|
+
* numeric string such as "-94.7" is read as its number). Team values may be numbers or strings (ESPN ids,
|
|
149
|
+
* abbreviations, names). The map needs the logos as images first:
|
|
150
|
+
* `await addLogoImages(map, logos)`, then `map.addSource(logos.id, logos.source)` and `map.addLayer(logos.layer)`.
|
|
151
|
+
*
|
|
152
|
+
* @example
|
|
153
|
+
* ```ts
|
|
154
|
+
* import { loadLeague } from "@sportsdataverse/sdvplot";
|
|
155
|
+
* import { logoSymbols } from "@sportsdataverse/sdvplot/maplibre";
|
|
156
|
+
*
|
|
157
|
+
* await loadLeague("nfl");
|
|
158
|
+
* // Kansas City's home airport, from nflverse's nfldata airports.csv
|
|
159
|
+
* const logos = logoSymbols([{ team: "KC", lon: -94.714, lat: 39.297 }], {
|
|
160
|
+
* x: "lon",
|
|
161
|
+
* y: "lat",
|
|
162
|
+
* team: "team",
|
|
163
|
+
* league: "nfl",
|
|
164
|
+
* size: 40,
|
|
165
|
+
* });
|
|
166
|
+
* // in the browser: await addLogoImages(map, logos); map.addSource(logos.id, logos.source); map.addLayer(logos.layer)
|
|
167
|
+
* logos.source.data.features[0]?.properties.icon;
|
|
168
|
+
* ```
|
|
169
|
+
*/
|
|
170
|
+
declare function logoSymbols(rows: readonly Row[], o: LogoSymbolOptions): LogoSymbols;
|
|
171
|
+
/**
|
|
172
|
+
* Loads each logo of `logoSymbols` sets into the map's images, once: `map.loadImage(url)`, then
|
|
173
|
+
* `map.addImage(id, image, { pixelRatio })`, the pixel ratio making the image draw `size` px tall at icon size 1 (and
|
|
174
|
+
* stay sharp on a high-density screen). An image the map already has is skipped, so calling it again adds nothing
|
|
175
|
+
* twice. `map.setStyle` drops the map's images, sources and layers: on the map's `style.load` event, call it again
|
|
176
|
+
* (it adds back only what the new style lacks) and re-add the source and layer. A logo that fails to load is left undrawn with one
|
|
177
|
+
* warning. Resolves when every image is in. Call it before adding the layers that draw them.
|
|
178
|
+
*
|
|
179
|
+
* @example
|
|
180
|
+
* ```ts
|
|
181
|
+
* import { loadLeague } from "@sportsdataverse/sdvplot";
|
|
182
|
+
* import { addLogoImages, logoSymbols } from "@sportsdataverse/sdvplot/maplibre";
|
|
183
|
+
*
|
|
184
|
+
* await loadLeague("nfl");
|
|
185
|
+
* const logos = logoSymbols([{ team: "KC", lon: -94.714, lat: 39.297 }], { x: "lon", y: "lat", team: "team", league: "nfl" });
|
|
186
|
+
* // a stand-in for a MapLibre map (in the browser: the map from `new maplibregl.Map(...)`, once it has loaded)
|
|
187
|
+
* const added: string[] = [];
|
|
188
|
+
* const map = {
|
|
189
|
+
* hasImage: (id: string) => added.includes(id),
|
|
190
|
+
* loadImage: async () => ({ data: { width: 500, height: 500 } as ImageBitmap }),
|
|
191
|
+
* addImage(id: string) {
|
|
192
|
+
* added.push(id);
|
|
193
|
+
* return this;
|
|
194
|
+
* },
|
|
195
|
+
* };
|
|
196
|
+
* await addLogoImages(map as never, logos);
|
|
197
|
+
* added;
|
|
198
|
+
* ```
|
|
199
|
+
*/
|
|
200
|
+
declare function addLogoImages(map: LogoImageMap, ...sets: readonly LogoSymbols[]): Promise<void>;
|
|
201
|
+
/**
|
|
202
|
+
* Team-coloured travel legs as a GeoJSON source and a MapLibre `line` layer: one straight line per row from (x, y) to
|
|
203
|
+
* (xend, yend) in longitude and latitude, in the team's colour (`line-color: ["get", "color"]`). Each team keeps one
|
|
204
|
+
* colour, from the shared team palette; with `background` the colours read on the basemap (dark mode) and stay
|
|
205
|
+
* distinct. A team that does not resolve takes `fallback`. A coordinate may be a number or a numeric string, as for
|
|
206
|
+
* `logoSymbols`; a row with a missing, non-numeric or infinite one is left out with one warning. The feature id is the row's index. The same rows feed `./deckgl`'s `passArcLayer` for arcs instead.
|
|
207
|
+
*
|
|
208
|
+
* @example
|
|
209
|
+
* ```ts
|
|
210
|
+
* import { loadLeague } from "@sportsdataverse/sdvplot";
|
|
211
|
+
* import { routeLines } from "@sportsdataverse/sdvplot/maplibre";
|
|
212
|
+
*
|
|
213
|
+
* await loadLeague("nfl");
|
|
214
|
+
* // Kansas City to Buffalo, airport to airport (nflverse nfldata airports.csv): week 11 of 2024
|
|
215
|
+
* const leg = { team: "KC", lon: -94.714, lat: 39.297, lon_end: -78.732, lat_end: 42.94 };
|
|
216
|
+
* const routes = routeLines([leg], { x: "lon", y: "lat", xend: "lon_end", yend: "lat_end", team: "team", league: "nfl" });
|
|
217
|
+
* // in the browser: map.addSource(routes.id, routes.source); map.addLayer(routes.layer)
|
|
218
|
+
* routes.source.data.features[0]?.properties.color;
|
|
219
|
+
* ```
|
|
220
|
+
*/
|
|
221
|
+
declare function routeLines(rows: readonly Row[], o: RouteLineOptions): RouteLines;
|
|
222
|
+
/** `basemap`'s result. */
|
|
223
|
+
interface Basemap {
|
|
224
|
+
/** The style URL, for the map's `style` option. */
|
|
225
|
+
style: string;
|
|
226
|
+
/**
|
|
227
|
+
* The provider's credit (HTML). MapLibre shows it by itself from the style's source, in its attribution control (on
|
|
228
|
+
* by default; keep it). Anything else that draws these tiles, or a static export of the map, must show it.
|
|
229
|
+
*/
|
|
230
|
+
attribution: string;
|
|
231
|
+
/**
|
|
232
|
+
* The colour of the style's `background` layer, the land (read from the style on 2026-10-27), for `routeLines`' and
|
|
233
|
+
* `passArcLayer`'s `background` (dark mode). Their 3:1 contrast is measured against it, not against the water,
|
|
234
|
+
* which is a little lighter on the dark style (#1b1b1d) and a little darker on the light one (#c2c8ca).
|
|
235
|
+
*/
|
|
236
|
+
background: string;
|
|
237
|
+
}
|
|
238
|
+
/**
|
|
239
|
+
* A free basemap that needs no API key: OpenFreeMap's `positron` (light) or `dark` vector style, built from
|
|
240
|
+
* OpenStreetMap data. Pass `style` to `new maplibregl.Map({ style })`. OpenFreeMap's public instance asks for no
|
|
241
|
+
* registration or key and sets no request limit; the credit is required, and MapLibre's attribution control shows it
|
|
242
|
+
* from the style's source (`attribution` is the same text, for anything else). Each map view requests the
|
|
243
|
+
* provider's tiles: build or test pages without a map, or serve a local style.
|
|
244
|
+
*
|
|
245
|
+
* @example
|
|
246
|
+
* ```ts
|
|
247
|
+
* import { basemap } from "@sportsdataverse/sdvplot/maplibre";
|
|
248
|
+
*
|
|
249
|
+
* const { style, background } = basemap("dark");
|
|
250
|
+
* // in the browser: new maplibregl.Map({ container, style, center: [-96, 38], zoom: 3 })
|
|
251
|
+
* [style, background];
|
|
252
|
+
* ```
|
|
253
|
+
*/
|
|
254
|
+
declare function basemap(theme?: "light" | "dark"): Basemap;
|
|
255
|
+
/**
|
|
256
|
+
* What `logoSymbols` sets draw, as `[team, longitude, latitude, px, url]` per feature, in order: the module's test
|
|
257
|
+
* hook, as the other adapters' `drawnMarks`.
|
|
258
|
+
*
|
|
259
|
+
* @example
|
|
260
|
+
* ```ts
|
|
261
|
+
* import { loadLeague } from "@sportsdataverse/sdvplot";
|
|
262
|
+
* import { drawnMarks, logoSymbols } from "@sportsdataverse/sdvplot/maplibre";
|
|
263
|
+
*
|
|
264
|
+
* await loadLeague("nfl");
|
|
265
|
+
* // 12 is Kansas City's ESPN team id
|
|
266
|
+
* drawnMarks(logoSymbols([{ team: 12, lon: -94.714, lat: 39.297 }], { x: "lon", y: "lat", team: "team", league: "nfl" }));
|
|
267
|
+
* ```
|
|
268
|
+
*/
|
|
269
|
+
declare function drawnMarks(...sets: readonly LogoSymbols[]): DrawnMark[];
|
|
270
|
+
|
|
271
|
+
export { type Basemap, DrawnMark, type Feature, type FeatureSource, type LineGeometry, type LogoImage, type LogoImageMap, type LogoProperties, type LogoSymbolOptions, type LogoSymbols, type PointGeometry, type RouteLineOptions, type RouteLines, type RouteProperties, Row, addLogoImages, basemap, drawnMarks, logoSymbols, routeLines };
|
package/dist/maplibre.js
ADDED
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
import { contentId } from './chunk-WQT4IVLX.js';
|
|
2
|
+
import { markPlacements, column, colorPalette } from './chunk-UL5M6KDE.js';
|
|
3
|
+
import { checkAlpha } from './chunk-7ULOCCFL.js';
|
|
4
|
+
import { resolveSync, normValue } from './chunk-ZAEQXFJM.js';
|
|
5
|
+
import './chunk-J6R2LEJ7.js';
|
|
6
|
+
import { warn, InputError } from './chunk-E7G7VSLV.js';
|
|
7
|
+
|
|
8
|
+
// src/maplibre.ts
|
|
9
|
+
var real = (v) => typeof v === "number" && Number.isFinite(v);
|
|
10
|
+
function coord(v) {
|
|
11
|
+
const n = typeof v === "number" ? v : typeof v === "string" && v.trim() !== "" ? Number(v) : Number.NaN;
|
|
12
|
+
return Number.isFinite(n) ? n : null;
|
|
13
|
+
}
|
|
14
|
+
function skippedRows(key, what, teams) {
|
|
15
|
+
if (teams.length === 0) return;
|
|
16
|
+
const shown = teams.slice(0, 10).map(String).join(", ") + (teams.length > 10 ? ` and ${teams.length - 10} more` : "");
|
|
17
|
+
warn(`${key}:${shown}`, `skipped ${teams.length} ${what} with a missing coordinate: ${shown}`);
|
|
18
|
+
}
|
|
19
|
+
function checkPx(name, v) {
|
|
20
|
+
if (!real(v) || v < 1) throw new InputError(`${name} is in px, 1 or more, got ${String(v)}`);
|
|
21
|
+
return v;
|
|
22
|
+
}
|
|
23
|
+
function logoSymbols(rows, o) {
|
|
24
|
+
const size = checkPx("size", o.size ?? 32);
|
|
25
|
+
const opacity = checkAlpha(o.alpha ?? 1);
|
|
26
|
+
const id = o.id ?? "sdvplot-logos";
|
|
27
|
+
const images = /* @__PURE__ */ new Map();
|
|
28
|
+
const placed = markPlacements(rows, "logo", o);
|
|
29
|
+
const at = placed.map((p) => [coord(p.x), coord(p.y)]);
|
|
30
|
+
skippedRows(
|
|
31
|
+
`maplibre:logos:${o.league}:missing`,
|
|
32
|
+
"logo(s)",
|
|
33
|
+
placed.filter((_, k) => at[k]?.includes(null)).map((p) => p.id)
|
|
34
|
+
);
|
|
35
|
+
const features = placed.flatMap((p, k) => {
|
|
36
|
+
const [lon, lat] = at[k];
|
|
37
|
+
if (lon === null || lat === null) return [];
|
|
38
|
+
const icon = `${contentId("sdv-logo", p.url)}-${size}`;
|
|
39
|
+
if (!images.has(icon)) images.set(icon, { id: icon, url: p.url, height: size });
|
|
40
|
+
return [
|
|
41
|
+
{
|
|
42
|
+
type: "Feature",
|
|
43
|
+
id: p.index,
|
|
44
|
+
geometry: { type: "Point", coordinates: [lon, lat] },
|
|
45
|
+
properties: { team: p.id, icon, url: p.url, index: p.index }
|
|
46
|
+
}
|
|
47
|
+
];
|
|
48
|
+
});
|
|
49
|
+
return {
|
|
50
|
+
id,
|
|
51
|
+
source: { type: "geojson", data: { type: "FeatureCollection", features } },
|
|
52
|
+
layer: {
|
|
53
|
+
id,
|
|
54
|
+
type: "symbol",
|
|
55
|
+
source: id,
|
|
56
|
+
layout: {
|
|
57
|
+
"icon-image": ["get", "icon"],
|
|
58
|
+
"icon-allow-overlap": true,
|
|
59
|
+
"icon-ignore-placement": true
|
|
60
|
+
},
|
|
61
|
+
paint: { "icon-opacity": opacity }
|
|
62
|
+
},
|
|
63
|
+
images: [...images.values()]
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
async function addLogoImages(map, ...sets) {
|
|
67
|
+
const want = new Map(sets.flatMap((s) => s.images).map((im) => [im.id, im]));
|
|
68
|
+
await Promise.all(
|
|
69
|
+
[...want.values()].map(async (im) => {
|
|
70
|
+
if (map.hasImage(im.id)) return;
|
|
71
|
+
let image;
|
|
72
|
+
try {
|
|
73
|
+
image = (await map.loadImage(im.url)).data;
|
|
74
|
+
} catch (e) {
|
|
75
|
+
warn(`maplibre:image:${im.url}`, `could not load the logo ${im.url}: ${String(e)}`);
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
if (map.hasImage(im.id)) return;
|
|
79
|
+
map.addImage(im.id, image, { pixelRatio: image.height / im.height });
|
|
80
|
+
})
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
var label = (t) => t === null || t === void 0 ? "" : typeof t === "number" ? normValue(t) ?? "" : String(t);
|
|
84
|
+
function routeLines(rows, o) {
|
|
85
|
+
const width = checkPx("width", o.width ?? 3);
|
|
86
|
+
const opacity = checkAlpha(o.alpha ?? 1);
|
|
87
|
+
const id = o.id ?? "sdvplot-routes";
|
|
88
|
+
const cols = [o.x, o.y, o.xend, o.yend].map(
|
|
89
|
+
(c, k) => column(rows, c, ["x", "y", "xend", "yend"][k])
|
|
90
|
+
);
|
|
91
|
+
const teams = column(rows, o.team, "team");
|
|
92
|
+
const at = rows.map((_, i) => cols.map((c) => coord(c[i])));
|
|
93
|
+
const keep = rows.flatMap((_, i) => at[i]?.includes(null) ? [] : [i]);
|
|
94
|
+
skippedRows(
|
|
95
|
+
`maplibre:routes:${o.league}:missing`,
|
|
96
|
+
"route leg(s)",
|
|
97
|
+
rows.flatMap((_, i) => at[i]?.includes(null) ? [teams[i]] : [])
|
|
98
|
+
);
|
|
99
|
+
const kept = keep.map((i) => teams[i]);
|
|
100
|
+
const ro = {
|
|
101
|
+
...o.season === void 0 ? {} : { season: o.season },
|
|
102
|
+
...o.idSystem === void 0 ? {} : { idSystem: o.idSystem }
|
|
103
|
+
};
|
|
104
|
+
const resolved = resolveSync(kept, o.league, ro);
|
|
105
|
+
const keys = kept.map((t, j) => resolved[j] ?? `?${label(t)}`);
|
|
106
|
+
const distinct = [...new Set(keys)];
|
|
107
|
+
const known = distinct.filter((k) => !k.startsWith("?"));
|
|
108
|
+
const palette = colorPalette(o.league, known, {
|
|
109
|
+
...ro,
|
|
110
|
+
idSystem: "team_id",
|
|
111
|
+
...o.which === void 0 ? {} : { which: o.which },
|
|
112
|
+
...o.background === void 0 ? {} : { background: o.background },
|
|
113
|
+
...o.fallback === void 0 ? {} : { fallback: o.fallback }
|
|
114
|
+
});
|
|
115
|
+
const colorOf = new Map(known.map((k, j) => [k, palette[j]]));
|
|
116
|
+
const fallback = o.fallback ?? "#808080";
|
|
117
|
+
const features = keep.map((i, j) => {
|
|
118
|
+
const c = at[i];
|
|
119
|
+
return {
|
|
120
|
+
type: "Feature",
|
|
121
|
+
id: i,
|
|
122
|
+
geometry: {
|
|
123
|
+
type: "LineString",
|
|
124
|
+
coordinates: [
|
|
125
|
+
[c[0], c[1]],
|
|
126
|
+
[c[2], c[3]]
|
|
127
|
+
]
|
|
128
|
+
},
|
|
129
|
+
properties: {
|
|
130
|
+
team: resolved[j] ?? label(kept[j]),
|
|
131
|
+
color: colorOf.get(keys[j]) ?? fallback,
|
|
132
|
+
index: i
|
|
133
|
+
}
|
|
134
|
+
};
|
|
135
|
+
});
|
|
136
|
+
return {
|
|
137
|
+
id,
|
|
138
|
+
source: { type: "geojson", data: { type: "FeatureCollection", features } },
|
|
139
|
+
layer: {
|
|
140
|
+
id,
|
|
141
|
+
type: "line",
|
|
142
|
+
source: id,
|
|
143
|
+
layout: { "line-cap": "round", "line-join": "round" },
|
|
144
|
+
paint: { "line-color": ["get", "color"], "line-width": width, "line-opacity": opacity }
|
|
145
|
+
}
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
var OPENFREEMAP_ATTRIBUTION = '<a href="https://openfreemap.org" target="_blank">OpenFreeMap</a> <a href="https://www.openmaptiles.org/" target="_blank">© OpenMapTiles</a> Data from <a href="https://www.openstreetmap.org/copyright" target="_blank">OpenStreetMap</a>';
|
|
149
|
+
var STYLES = {
|
|
150
|
+
light: { style: "https://tiles.openfreemap.org/styles/positron", background: "#f2f3f0" },
|
|
151
|
+
dark: { style: "https://tiles.openfreemap.org/styles/dark", background: "#0c0c0c" }
|
|
152
|
+
};
|
|
153
|
+
function basemap(theme = "light") {
|
|
154
|
+
const s = STYLES[theme];
|
|
155
|
+
if (s === void 0) throw new InputError(`theme is "light" or "dark", got ${JSON.stringify(theme)}`);
|
|
156
|
+
return { ...s, attribution: OPENFREEMAP_ATTRIBUTION };
|
|
157
|
+
}
|
|
158
|
+
function drawnMarks(...sets) {
|
|
159
|
+
return sets.flatMap((s) => {
|
|
160
|
+
const px = new Map(s.images.map((im) => [im.id, im.height]));
|
|
161
|
+
return s.source.data.features.map(
|
|
162
|
+
(f) => [
|
|
163
|
+
f.properties.team,
|
|
164
|
+
f.geometry.coordinates[0],
|
|
165
|
+
f.geometry.coordinates[1],
|
|
166
|
+
px.get(f.properties.icon),
|
|
167
|
+
f.properties.url
|
|
168
|
+
]
|
|
169
|
+
);
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
export { addLogoImages, basemap, drawnMarks, logoSymbols, routeLines };
|
|
174
|
+
//# sourceMappingURL=maplibre.js.map
|
|
175
|
+
//# sourceMappingURL=maplibre.js.map
|