@sportsdataverse/sdvplot 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/NOTICE.md +15 -0
- package/README.md +84 -8
- package/dist/chartjs-surface.d.ts +2 -2
- package/dist/chartjs-surface.js +2 -2
- package/dist/chartjs-surface.js.map +1 -1
- package/dist/chartjs.d.ts +32 -2
- package/dist/chartjs.js +7 -4
- package/dist/chartjs.js.map +1 -1
- package/dist/chunk-57WFSI4Q.js +128 -0
- package/dist/chunk-57WFSI4Q.js.map +1 -0
- package/dist/{chunk-KKH6ABMA.js → chunk-7ULOCCFL.js} +94 -4
- package/dist/chunk-7ULOCCFL.js.map +1 -0
- package/dist/chunk-AMXASQHT.js +120 -0
- package/dist/chunk-AMXASQHT.js.map +1 -0
- package/dist/chunk-B2MP37SR.js +175 -0
- package/dist/chunk-B2MP37SR.js.map +1 -0
- package/dist/{chunk-NLPH2VZT.js → chunk-DYMUVE6K.js} +4 -4
- package/dist/{chunk-NLPH2VZT.js.map → chunk-DYMUVE6K.js.map} +1 -1
- package/dist/{chunk-QY65DUEF.js → chunk-H2C3FMMA.js} +4 -10
- package/dist/chunk-H2C3FMMA.js.map +1 -0
- package/dist/{chunk-JJFTD4VB.js → chunk-J4P4HDBF.js} +4 -4
- package/dist/{chunk-JJFTD4VB.js.map → chunk-J4P4HDBF.js.map} +1 -1
- package/dist/chunk-M3XD2OSN.js +132 -0
- package/dist/chunk-M3XD2OSN.js.map +1 -0
- package/dist/{chunk-OKEE4AH6.js → chunk-OIILARXI.js} +3 -3
- package/dist/{chunk-OKEE4AH6.js.map → chunk-OIILARXI.js.map} +1 -1
- package/dist/chunk-RPVVJY74.js +170 -0
- package/dist/chunk-RPVVJY74.js.map +1 -0
- package/dist/chunk-UL5M6KDE.js +205 -0
- package/dist/chunk-UL5M6KDE.js.map +1 -0
- package/dist/chunk-WQT4IVLX.js +10 -0
- package/dist/chunk-WQT4IVLX.js.map +1 -0
- package/dist/{chunk-TAXO3VXH.js → chunk-ZAEQXFJM.js} +3 -3
- package/dist/{chunk-TAXO3VXH.js.map → chunk-ZAEQXFJM.js.map} +1 -1
- package/dist/cytoscape.d.ts +188 -0
- package/dist/cytoscape.js +219 -0
- package/dist/cytoscape.js.map +1 -0
- package/dist/d3.d.ts +3 -3
- package/dist/d3.js +6 -6
- package/dist/d3.js.map +1 -1
- package/dist/deckgl.d.ts +232 -0
- package/dist/deckgl.js +197 -0
- package/dist/deckgl.js.map +1 -0
- package/dist/echarts.js +5 -5
- package/dist/g2.d.ts +251 -0
- package/dist/g2.js +102 -0
- package/dist/g2.js.map +1 -0
- package/dist/index.d.ts +2 -1
- package/dist/index.js +4 -5
- package/dist/lightweight-charts.d.ts +217 -0
- package/dist/lightweight-charts.js +155 -0
- package/dist/lightweight-charts.js.map +1 -0
- package/dist/maplibre.d.ts +271 -0
- package/dist/maplibre.js +175 -0
- package/dist/maplibre.js.map +1 -0
- package/dist/mui.d.ts +338 -0
- package/dist/mui.js +184 -0
- package/dist/mui.js.map +1 -0
- package/dist/nivo.d.ts +193 -0
- package/dist/nivo.js +8 -0
- package/dist/nivo.js.map +1 -0
- package/dist/perspective.d.ts +198 -0
- package/dist/perspective.js +171 -0
- package/dist/perspective.js.map +1 -0
- package/dist/placement-UWkqUzpa.d.ts +32 -0
- package/dist/plot.d.ts +7 -5
- package/dist/plot.js +9 -8
- package/dist/plot.js.map +1 -1
- package/dist/plotly.js +5 -5
- package/dist/react.d.ts +279 -3
- package/dist/react.js +364 -6
- package/dist/react.js.map +1 -1
- package/dist/recharts.d.ts +162 -0
- package/dist/recharts.js +8 -0
- package/dist/recharts.js.map +1 -0
- package/dist/shadcn.d.ts +83 -0
- package/dist/shadcn.js +88 -0
- package/dist/shadcn.js.map +1 -0
- package/dist/shots.js +3 -2
- package/dist/shots.js.map +1 -1
- package/dist/surface-TR7YFJW3.js +4 -0
- package/dist/surface-TR7YFJW3.js.map +1 -0
- package/dist/surfaces-MIG7HOFU.js +6 -0
- package/dist/surfaces-MIG7HOFU.js.map +1 -0
- package/dist/surfaces-W-odWLpT.d.ts +47 -0
- package/dist/svelte/HeadshotMark.svelte +25 -0
- package/dist/svelte/HeadshotMark.svelte.d.ts +10 -0
- package/dist/svelte/HeadshotMark.svelte.d.ts.map +1 -0
- package/dist/svelte/LogoMark.svelte +25 -0
- package/dist/svelte/LogoMark.svelte.d.ts +10 -0
- package/dist/svelte/LogoMark.svelte.d.ts.map +1 -0
- package/dist/svelte/Marks.svelte +25 -0
- package/dist/svelte/Marks.svelte.d.ts +10 -0
- package/dist/svelte/Marks.svelte.d.ts.map +1 -0
- package/dist/svelte/Surface.svelte +29 -0
- package/dist/svelte/Surface.svelte.d.ts +9 -0
- package/dist/svelte/Surface.svelte.d.ts.map +1 -0
- package/dist/svelte/SurfaceBackdrop.svelte +26 -0
- package/dist/svelte/SurfaceBackdrop.svelte.d.ts +11 -0
- package/dist/svelte/SurfaceBackdrop.svelte.d.ts.map +1 -0
- package/dist/svelte/index.d.ts +13 -0
- package/dist/svelte/index.d.ts.map +1 -0
- package/dist/svelte/index.js +11 -0
- package/dist/svelte-core.d.ts +139 -0
- package/dist/svelte-core.js +123 -0
- package/dist/svelte-core.js.map +1 -0
- package/dist/tanstack.d.ts +240 -0
- package/dist/tanstack.js +110 -0
- package/dist/tanstack.js.map +1 -0
- package/dist/testing.js +4 -5
- package/dist/testing.js.map +1 -1
- package/dist/three.d.ts +223 -0
- package/dist/three.js +50 -0
- package/dist/three.js.map +1 -0
- package/dist/{tiers-B6pUBPcE.d.ts → tiers-BN4md-GB.d.ts} +2 -30
- package/dist/unovis.d.ts +127 -0
- package/dist/unovis.js +108 -0
- package/dist/unovis.js.map +1 -0
- package/dist/uplot.d.ts +120 -0
- package/dist/uplot.js +110 -0
- package/dist/uplot.js.map +1 -0
- package/dist/vega.js +5 -5
- package/dist/victory.d.ts +107 -0
- package/dist/victory.js +38 -0
- package/dist/victory.js.map +1 -0
- package/package.json +218 -6
- package/dist/chunk-KKH6ABMA.js.map +0 -1
- package/dist/chunk-QY65DUEF.js.map +0 -1
- package/dist/chunk-RMT2GMFH.js +0 -97
- package/dist/chunk-RMT2GMFH.js.map +0 -1
- package/dist/chunk-SEBO35DV.js +0 -76
- package/dist/chunk-SEBO35DV.js.map +0 -1
- package/dist/chunk-TDNBAEQB.js +0 -105
- package/dist/chunk-TDNBAEQB.js.map +0 -1
- package/dist/surfaces-ChaD9nei.d.ts +0 -38
package/dist/mui.d.ts
ADDED
|
@@ -0,0 +1,338 @@
|
|
|
1
|
+
import { ReactElement } from 'react';
|
|
2
|
+
import { E as EspnHeadshotLeague } from './headshots-B2J_LOO8.js';
|
|
3
|
+
import { V as Value } from './resolve-CQW-jzvr.js';
|
|
4
|
+
import { W as Which, S as SeasonInput, I as IdSystem, H as HeadshotIdSystem, L as League, V as Variant, M as MarkType } from './types-C7n6gJF3.js';
|
|
5
|
+
export { e as embedSources } from './_web-D7Aa5Rd8.js';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* The MUI X Charts adapter (`@mui/x-charts` 9, the MIT community package): team colours for a chart's `colors` (a
|
|
9
|
+
* palette that follows MUI's light and dark mode) or a series' `colorGetter` (a colour per data point), and team logos
|
|
10
|
+
* and player headshots through MUI's own slots: a scatter's `marker`, a line's `mark` and an axis' `axisTickLabel`.
|
|
11
|
+
* Pure: MUI is never imported (the slot props here are structural, read from `@mui/x-charts` 9.15 and checked against
|
|
12
|
+
* its own types); React's `createElement` builds the SVG `<image>` each slot returns, so the logos also render on the
|
|
13
|
+
* server. Call after `loadLeague(league)` / `preloadAll()`. `sdvplot/testing`'s `drawnMarks(container)` reads back
|
|
14
|
+
* what the slots drew.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
/** `teamColorPalette` and `teamColorGetter` options. */
|
|
18
|
+
interface MuiColorOptions {
|
|
19
|
+
/** "primary" (default) or "secondary" colour. */
|
|
20
|
+
which?: Which;
|
|
21
|
+
/** The season the teams are resolved for (a number or string). */
|
|
22
|
+
season?: SeasonInput;
|
|
23
|
+
/** How the team values are read. Default "auto". */
|
|
24
|
+
idSystem?: IdSystem;
|
|
25
|
+
/** The colour of an unresolved team. Default "#808080". */
|
|
26
|
+
fallback?: string;
|
|
27
|
+
/**
|
|
28
|
+
* The chart's background. Given, a team colour under 3:1 contrast against it is swapped for the team's other
|
|
29
|
+
* colour, else moved towards white (on a dark background) or black (a light one) until it reads: dark mode. A
|
|
30
|
+
* colour that would then sit too close to an earlier team's (CIE76 ΔE under 20) takes the team's other colour, or
|
|
31
|
+
* the nearest colour to its own (lightness, hue or chroma stepped) that is distinct and still reads; failing that it
|
|
32
|
+
* is kept, with one warning.
|
|
33
|
+
*/
|
|
34
|
+
background?: string;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* One colour per team, in order: a chart's `colors` (series i takes colour i) or a series' `color`. With
|
|
38
|
+
* `background`, every colour keeps 3:1 contrast against it (dark mode) and aims to stay distinct from the others.
|
|
39
|
+
* `teamColors` gives the same colours as a palette that follows MUI's light and dark mode.
|
|
40
|
+
*
|
|
41
|
+
* @example
|
|
42
|
+
* ```tsx
|
|
43
|
+
* import { loadLeague } from "@sportsdataverse/sdvplot";
|
|
44
|
+
* import { teamColorPalette } from "@sportsdataverse/sdvplot/mui";
|
|
45
|
+
*
|
|
46
|
+
* await loadLeague("nfl");
|
|
47
|
+
* teamColorPalette("nfl", ["KC", "LAC", "DEN", "LV"], { background: "#121212" });
|
|
48
|
+
* ```
|
|
49
|
+
*/
|
|
50
|
+
declare function teamColorPalette(league: League, teams: readonly unknown[], o?: MuiColorOptions): string[];
|
|
51
|
+
/** `teamColors` options: the backgrounds the light and dark palettes keep 3:1 contrast against. */
|
|
52
|
+
interface MuiPaletteOptions extends Omit<MuiColorOptions, "background"> {
|
|
53
|
+
/** The chart's background in each mode. Default MUI's own: `{ light: "#ffffff", dark: "#121212" }`. */
|
|
54
|
+
background?: {
|
|
55
|
+
light?: string;
|
|
56
|
+
dark?: string;
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
/** MUI's `colors` callback: the palette for the theme's mode. */
|
|
60
|
+
type MuiColorPalette = (mode: "light" | "dark") => string[];
|
|
61
|
+
/**
|
|
62
|
+
* Team colours as a chart's `colors`, MUI's palette callback: MUI calls it with the theme's mode, so under a dark
|
|
63
|
+
* `ThemeProvider` every series takes its team colour readable on the dark background (3:1 contrast, distinct from the
|
|
64
|
+
* others), and under a light one readable on white. Series i takes `teams[i]`'s colour: give the series in the same
|
|
65
|
+
* order. Each mode's palette is worked out once, when MUI first asks for it, so build it once (module scope or
|
|
66
|
+
* `useMemo`): a new one on every render works the palettes out again.
|
|
67
|
+
*
|
|
68
|
+
* @example
|
|
69
|
+
* ```tsx
|
|
70
|
+
* import { createTheme, ThemeProvider } from "@mui/material/styles";
|
|
71
|
+
* import { LineChart } from "@mui/x-charts/LineChart";
|
|
72
|
+
* import { loadLeague } from "@sportsdataverse/sdvplot";
|
|
73
|
+
* import { teamColors } from "@sportsdataverse/sdvplot/mui";
|
|
74
|
+
*
|
|
75
|
+
* await loadLeague("nfl");
|
|
76
|
+
* const teams = ["KC", "DEN"];
|
|
77
|
+
* <ThemeProvider theme={createTheme({ palette: { mode: "dark" } })}>
|
|
78
|
+
* <LineChart
|
|
79
|
+
* width={400}
|
|
80
|
+
* height={240}
|
|
81
|
+
* colors={teamColors("nfl", teams)}
|
|
82
|
+
* xAxis={[{ data: [1, 2, 3] }]}
|
|
83
|
+
* series={[
|
|
84
|
+
* { id: "KC", data: [1, 2, 3] },
|
|
85
|
+
* { id: "DEN", data: [0, 1, 2] },
|
|
86
|
+
* ]}
|
|
87
|
+
* />
|
|
88
|
+
* </ThemeProvider>;
|
|
89
|
+
* ```
|
|
90
|
+
*/
|
|
91
|
+
declare function teamColors(league: League, teams: readonly unknown[], o?: MuiPaletteOptions): MuiColorPalette;
|
|
92
|
+
/** What MUI hands a series' `colorGetter`: the data point's index (and value, unused here). */
|
|
93
|
+
interface MuiColorCallbackValue {
|
|
94
|
+
/** The data point's index in its series. */
|
|
95
|
+
dataIndex: number;
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* A colour per data point, for a series' `colorGetter` (bar, scatter, line): `teams[i]` is the team of data point
|
|
99
|
+
* i, so each bar or point takes its own team's colour. A team that repeats keeps one colour. With `background`, every
|
|
100
|
+
* colour keeps 3:1 contrast against it (dark mode). A point past the end of `teams` takes `fallback`.
|
|
101
|
+
*
|
|
102
|
+
* @example
|
|
103
|
+
* ```tsx
|
|
104
|
+
* import { BarChart } from "@mui/x-charts/BarChart";
|
|
105
|
+
* import { loadLeague } from "@sportsdataverse/sdvplot";
|
|
106
|
+
* import { teamColorGetter } from "@sportsdataverse/sdvplot/mui";
|
|
107
|
+
*
|
|
108
|
+
* await loadLeague("nfl");
|
|
109
|
+
* const teams = ["KC", "BUF", "BAL"];
|
|
110
|
+
* <BarChart
|
|
111
|
+
* width={400}
|
|
112
|
+
* height={240}
|
|
113
|
+
* xAxis={[{ scaleType: "band", data: teams }]}
|
|
114
|
+
* series={[{ data: [15, 13, 12], colorGetter: teamColorGetter("nfl", teams) }]}
|
|
115
|
+
* />;
|
|
116
|
+
* ```
|
|
117
|
+
*/
|
|
118
|
+
declare function teamColorGetter(league: League, teams: readonly unknown[], o?: MuiColorOptions): (d: MuiColorCallbackValue) => string;
|
|
119
|
+
/** What MUI hands a scatter's `marker` slot: the subset read here. */
|
|
120
|
+
interface MuiMarkerProps {
|
|
121
|
+
/** The series the point belongs to. */
|
|
122
|
+
seriesId: string;
|
|
123
|
+
/** The point's index in its series' data. */
|
|
124
|
+
dataIndex: number;
|
|
125
|
+
/** The point's centre in px. */
|
|
126
|
+
x: number;
|
|
127
|
+
/** The point's centre in px. */
|
|
128
|
+
y: number;
|
|
129
|
+
/** The marker's radius in px: the series' `markerSize`. The logo is twice it tall. */
|
|
130
|
+
size: number;
|
|
131
|
+
/** MUI highlights the point (the pointer is on it or its series): drawn 1.2 times as tall. */
|
|
132
|
+
isHighlighted?: boolean;
|
|
133
|
+
/** MUI fades the point (another series is highlighted): drawn at 0.3 of the opacity. */
|
|
134
|
+
isFaded?: boolean;
|
|
135
|
+
/** MUI's click handler, kept on the image. */
|
|
136
|
+
onClick?: ((event: never) => void) | undefined;
|
|
137
|
+
}
|
|
138
|
+
/** What MUI hands a line's `mark` slot: the subset read here. */
|
|
139
|
+
interface MuiMarkProps {
|
|
140
|
+
/** The series the mark belongs to: by default its id is the team. */
|
|
141
|
+
seriesId: string;
|
|
142
|
+
/** The mark's index along the x axis. */
|
|
143
|
+
dataIndex: number;
|
|
144
|
+
/** The mark's centre in px. */
|
|
145
|
+
x?: number | string | undefined;
|
|
146
|
+
/** The mark's centre in px. */
|
|
147
|
+
y?: number | string | undefined;
|
|
148
|
+
/** The series is hidden (from its legend): nothing is drawn. */
|
|
149
|
+
hidden?: boolean | undefined;
|
|
150
|
+
/** MUI highlights the mark; a logo keeps its size. */
|
|
151
|
+
isHighlighted?: boolean | undefined;
|
|
152
|
+
/** MUI fades the series (another is highlighted): drawn at 0.3 of the opacity. */
|
|
153
|
+
isFaded?: boolean | undefined;
|
|
154
|
+
/** MUI's click handler, kept on the image. */
|
|
155
|
+
onClick?: ((event: never) => void) | undefined;
|
|
156
|
+
}
|
|
157
|
+
/** What MUI hands an axis' `axisTickLabel` slot: the subset read here. */
|
|
158
|
+
interface MuiTickLabelProps {
|
|
159
|
+
/** The label's anchor in px (in its tick's frame). */
|
|
160
|
+
x?: number | string | undefined;
|
|
161
|
+
/** The label's anchor in px (in its tick's frame). */
|
|
162
|
+
y?: number | string | undefined;
|
|
163
|
+
/** The tick's label: the team, as the axis' `valueFormatter` wrote it. */
|
|
164
|
+
text?: string | undefined;
|
|
165
|
+
/** The label's style: its `textAnchor` and `dominantBaseline` say which side of the anchor it runs. */
|
|
166
|
+
style?: {
|
|
167
|
+
textAnchor?: string | undefined;
|
|
168
|
+
dominantBaseline?: string | undefined;
|
|
169
|
+
} | undefined;
|
|
170
|
+
/** MUI's tick label class, kept on the image or the text. */
|
|
171
|
+
className?: string | undefined;
|
|
172
|
+
}
|
|
173
|
+
/** A scatter's `marker` slot: `<ScatterChart slots={{ marker }}>`. */
|
|
174
|
+
type MuiMarkerSlot = (props: MuiMarkerProps) => ReactElement | null;
|
|
175
|
+
/** A line's `mark` slot: `<LineChart slots={{ mark }}>`. */
|
|
176
|
+
type MuiMarkSlot = (props: MuiMarkProps) => ReactElement | null;
|
|
177
|
+
/** An axis' `axisTickLabel` slot: `<ChartsXAxis slots={{ axisTickLabel }}>`. */
|
|
178
|
+
type MuiTickLabelSlot = (props: MuiTickLabelProps) => ReactElement;
|
|
179
|
+
/** The team (or player) of each data point: one list for every series, or a list per series id. */
|
|
180
|
+
type MuiPointValues = readonly Value[] | Readonly<Record<string, readonly Value[]>>;
|
|
181
|
+
interface Shared {
|
|
182
|
+
/** Opacity in [0, 1]. Default 1. MUI's faded points (another series highlighted) draw at 0.3 of it. */
|
|
183
|
+
alpha?: number;
|
|
184
|
+
/** url → data URI map from `embedSources`, to inline the images. */
|
|
185
|
+
embed?: ReadonlyMap<string, string>;
|
|
186
|
+
}
|
|
187
|
+
interface TeamShared extends Shared {
|
|
188
|
+
/** The league the teams belong to, e.g. "nfl". */
|
|
189
|
+
league: League;
|
|
190
|
+
/** The season the teams are resolved for. */
|
|
191
|
+
season?: SeasonInput;
|
|
192
|
+
/** Logo variant, e.g. "dark". Default "default". */
|
|
193
|
+
variant?: Variant;
|
|
194
|
+
/** "logo" (default) or "wordmark". */
|
|
195
|
+
markType?: MarkType;
|
|
196
|
+
/** How the team values are read. Default "auto". */
|
|
197
|
+
idSystem?: IdSystem;
|
|
198
|
+
}
|
|
199
|
+
/** `logoMarker` options. */
|
|
200
|
+
interface MuiMarkerOptions extends TeamShared {
|
|
201
|
+
/** The team of each data point (abbreviation, name or id; see `idSystem`): `teams[i]` is point i's. */
|
|
202
|
+
teams: MuiPointValues;
|
|
203
|
+
}
|
|
204
|
+
/** `headshotMarker` options. */
|
|
205
|
+
interface MuiHeadshotOptions extends Shared {
|
|
206
|
+
/** The player of each data point: `players[i]` is point i's. */
|
|
207
|
+
players: MuiPointValues;
|
|
208
|
+
/** The league the players belong to, e.g. "nfl". */
|
|
209
|
+
league: EspnHeadshotLeague;
|
|
210
|
+
/** ESPN athlete ids, or nflverse gsis ids (needs `loadGsis()` first). Default "espn". */
|
|
211
|
+
idSystem?: HeadshotIdSystem;
|
|
212
|
+
}
|
|
213
|
+
/** `logoMark` options. */
|
|
214
|
+
interface MuiMarkOptions extends TeamShared {
|
|
215
|
+
/** Each series' team by series id. Default: the series id is the team. */
|
|
216
|
+
teams?: Readonly<Record<string, Value>>;
|
|
217
|
+
/** The logo's height in px, 1 or more (MUI hands a line mark no size). Default 24. */
|
|
218
|
+
size?: number;
|
|
219
|
+
}
|
|
220
|
+
/** `logoTickLabel` options. */
|
|
221
|
+
interface MuiTickLabelOptions extends Omit<TeamShared, "alpha"> {
|
|
222
|
+
/** The logo's height in px, 1 or more. Default 24: give the axis room (an x axis' `height`, a y axis' `width`). */
|
|
223
|
+
size?: number;
|
|
224
|
+
}
|
|
225
|
+
/**
|
|
226
|
+
* Team logos as a scatter's points: `<ScatterChart slots={{ marker: logoMarker({ league, teams }) }}>`. Point i is
|
|
227
|
+
* drawn as the logo of `teams[i]` (or `teams[seriesId][i]`), centred on the point and twice the series'
|
|
228
|
+
* `markerSize` tall, in px: set `markerSize` on the series. MUI's tooltip, highlight (1.2 times as tall) and fade
|
|
229
|
+
* (0.3 opacity) keep working. A team with no logo draws nothing and warns once, as `sdvplot/echarts` does.
|
|
230
|
+
* Synchronous: call it after `loadLeague(league)`. Build it once (module scope or `useMemo`): a new slot on every
|
|
231
|
+
* render remounts the points.
|
|
232
|
+
*
|
|
233
|
+
* @example
|
|
234
|
+
* ```tsx
|
|
235
|
+
* import { ScatterChart } from "@mui/x-charts/ScatterChart";
|
|
236
|
+
* import { loadLeague } from "@sportsdataverse/sdvplot";
|
|
237
|
+
* import { logoMarker } from "@sportsdataverse/sdvplot/mui";
|
|
238
|
+
*
|
|
239
|
+
* await loadLeague("nfl");
|
|
240
|
+
* const rows = [
|
|
241
|
+
* { team: "KC", pf: 385, pa: 326 },
|
|
242
|
+
* { team: "LAC", pf: 402, pa: 301 },
|
|
243
|
+
* { team: "DEN", pf: 425, pa: 311 },
|
|
244
|
+
* ];
|
|
245
|
+
* const marker = logoMarker({ league: "nfl", teams: rows.map((r) => r.team) });
|
|
246
|
+
* <ScatterChart
|
|
247
|
+
* width={400}
|
|
248
|
+
* height={300}
|
|
249
|
+
* series={[{ data: rows.map((r) => ({ x: r.pf, y: r.pa })), markerSize: 14 }]}
|
|
250
|
+
* slots={{ marker }}
|
|
251
|
+
* />;
|
|
252
|
+
* ```
|
|
253
|
+
*/
|
|
254
|
+
declare function logoMarker(o: MuiMarkerOptions): MuiMarkerSlot;
|
|
255
|
+
/**
|
|
256
|
+
* ESPN player headshots as a scatter's points (`marker` slot), from `players[i]` for point i; twice the series'
|
|
257
|
+
* `markerSize` tall. An id with no headshot draws nothing and warns once. `idSystem: "gsis"` needs `loadGsis()` first.
|
|
258
|
+
*
|
|
259
|
+
* @example
|
|
260
|
+
* ```tsx
|
|
261
|
+
* import { ScatterChart } from "@mui/x-charts/ScatterChart";
|
|
262
|
+
* import { headshotMarker } from "@sportsdataverse/sdvplot/mui";
|
|
263
|
+
*
|
|
264
|
+
* // Patrick Mahomes (ESPN 3139477): 2024, 3,928 passing yards and 26 touchdowns
|
|
265
|
+
* <ScatterChart
|
|
266
|
+
* width={400}
|
|
267
|
+
* height={300}
|
|
268
|
+
* series={[{ data: [{ x: 3928, y: 26 }], markerSize: 20 }]}
|
|
269
|
+
* slots={{ marker: headshotMarker({ league: "nfl", players: ["3139477"] }) }}
|
|
270
|
+
* />;
|
|
271
|
+
* ```
|
|
272
|
+
*/
|
|
273
|
+
declare function headshotMarker(o: MuiHeadshotOptions): MuiMarkerSlot;
|
|
274
|
+
/**
|
|
275
|
+
* Team logos as a line's marks: `<LineChart slots={{ mark: logoMark({ league }) }}>`. Each mark is drawn as its
|
|
276
|
+
* series' team logo, `size` px tall and centred on the point: the series' id is the team (or give `teams`, series id
|
|
277
|
+
* to team). Which points get a mark is the series' `showMark`: `({ index }) => index === last` puts one logo at the
|
|
278
|
+
* end of each line. A team with no logo draws nothing and warns once. Build it once (module scope or `useMemo`).
|
|
279
|
+
*
|
|
280
|
+
* @example
|
|
281
|
+
* ```tsx
|
|
282
|
+
* import { LineChart } from "@mui/x-charts/LineChart";
|
|
283
|
+
* import { loadLeague } from "@sportsdataverse/sdvplot";
|
|
284
|
+
* import { logoMark } from "@sportsdataverse/sdvplot/mui";
|
|
285
|
+
*
|
|
286
|
+
* await loadLeague("nfl");
|
|
287
|
+
* const end = ({ index }: { index: number }) => index === 2; // the last of three points
|
|
288
|
+
* <LineChart
|
|
289
|
+
* width={400}
|
|
290
|
+
* height={240}
|
|
291
|
+
* xAxis={[{ data: [1, 2, 3] }]}
|
|
292
|
+
* series={[
|
|
293
|
+
* { id: "KC", data: [1, 2, 3], showMark: end },
|
|
294
|
+
* { id: "DEN", data: [0, 1, 2], showMark: end },
|
|
295
|
+
* ]}
|
|
296
|
+
* slots={{ mark: logoMark({ league: "nfl", size: 28 }) }}
|
|
297
|
+
* />;
|
|
298
|
+
* ```
|
|
299
|
+
*/
|
|
300
|
+
declare function logoMark(o: MuiMarkOptions): MuiMarkSlot;
|
|
301
|
+
/**
|
|
302
|
+
* Team logos as an axis' tick labels: `<ChartsXAxis slots={{ axisTickLabel: logoTickLabel({ league }) }} />` on a
|
|
303
|
+
* band axis whose values are teams. The logo sits where MUI would put the label (under a bottom axis, left of a left
|
|
304
|
+
* one), `size` px tall; give the axis room for it (an x axis' `height`, a y axis' `width`). A label that resolves to
|
|
305
|
+
* no logo keeps its text and warns once, as `sdvplot/echarts`' `withAxisLogos` does. The label is the axis'
|
|
306
|
+
* formatted value, so a `valueFormatter` must keep it a team (abbreviation, name or id).
|
|
307
|
+
*
|
|
308
|
+
* Give it to the team axis alone, as a prop of the composition API's `<ChartsXAxis>` / `<ChartsYAxis>`: a chart's
|
|
309
|
+
* `slots` reach every axis (a value axis' label "12" would resolve as team id 12), and in `@mui/x-charts` 9.15 the
|
|
310
|
+
* `slots` of an axis in the `xAxis` prop are dropped by `BarChart` and the other single-component charts.
|
|
311
|
+
*
|
|
312
|
+
* @example
|
|
313
|
+
* ```tsx
|
|
314
|
+
* import { BarPlot } from "@mui/x-charts/BarChart";
|
|
315
|
+
* import { ChartsContainer } from "@mui/x-charts/ChartsContainer";
|
|
316
|
+
* import { ChartsXAxis } from "@mui/x-charts/ChartsXAxis";
|
|
317
|
+
* import { ChartsYAxis } from "@mui/x-charts/ChartsYAxis";
|
|
318
|
+
* import { loadLeague } from "@sportsdataverse/sdvplot";
|
|
319
|
+
* import { logoTickLabel } from "@sportsdataverse/sdvplot/mui";
|
|
320
|
+
*
|
|
321
|
+
* await loadLeague("nfl");
|
|
322
|
+
* const teams = ["KC", "BUF", "BAL"];
|
|
323
|
+
* <ChartsContainer
|
|
324
|
+
* width={400}
|
|
325
|
+
* height={260}
|
|
326
|
+
* xAxis={[{ id: "teams", scaleType: "band", data: teams, height: 40 }]}
|
|
327
|
+
* yAxis={[{ id: "wins" }]}
|
|
328
|
+
* series={[{ type: "bar", data: [15, 13, 12] }]}
|
|
329
|
+
* >
|
|
330
|
+
* <BarPlot />
|
|
331
|
+
* <ChartsXAxis axisId="teams" slots={{ axisTickLabel: logoTickLabel({ league: "nfl", size: 28 }) }} />
|
|
332
|
+
* <ChartsYAxis axisId="wins" />
|
|
333
|
+
* </ChartsContainer>;
|
|
334
|
+
* ```
|
|
335
|
+
*/
|
|
336
|
+
declare function logoTickLabel(o: MuiTickLabelOptions): MuiTickLabelSlot;
|
|
337
|
+
|
|
338
|
+
export { type MuiColorCallbackValue, type MuiColorOptions, type MuiColorPalette, type MuiHeadshotOptions, type MuiMarkOptions, type MuiMarkProps, type MuiMarkSlot, type MuiMarkerOptions, type MuiMarkerProps, type MuiMarkerSlot, type MuiPaletteOptions, type MuiPointValues, type MuiTickLabelOptions, type MuiTickLabelProps, type MuiTickLabelSlot, headshotMarker, logoMark, logoMarker, logoTickLabel, teamColorGetter, teamColorPalette, teamColors };
|
package/dist/mui.js
ADDED
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
import { colorPalette, aspect, imageSource } from './chunk-UL5M6KDE.js';
|
|
2
|
+
export { embedSources } from './chunk-UL5M6KDE.js';
|
|
3
|
+
import { placeSync, checkAlpha, placedName } from './chunk-7ULOCCFL.js';
|
|
4
|
+
import './chunk-ZAEQXFJM.js';
|
|
5
|
+
import './chunk-J6R2LEJ7.js';
|
|
6
|
+
import { warn, InputError } from './chunk-E7G7VSLV.js';
|
|
7
|
+
import { createElement } from 'react';
|
|
8
|
+
|
|
9
|
+
function teamColorPalette(league, teams, o = {}) {
|
|
10
|
+
return colorPalette(league, teams, o);
|
|
11
|
+
}
|
|
12
|
+
function teamColors(league, teams, o = {}) {
|
|
13
|
+
const { background, ...co } = o;
|
|
14
|
+
const bg = { light: background?.light ?? "#ffffff", dark: background?.dark ?? "#121212" };
|
|
15
|
+
const memo = {};
|
|
16
|
+
return (mode) => {
|
|
17
|
+
const m = mode === "dark" ? "dark" : "light";
|
|
18
|
+
memo[m] ??= colorPalette(league, teams, { ...co, background: bg[m] });
|
|
19
|
+
return [...memo[m]];
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
function teamColorGetter(league, teams, o = {}) {
|
|
23
|
+
const colors = colorPalette(league, teams, o);
|
|
24
|
+
const fallback = o.fallback ?? "#808080";
|
|
25
|
+
return ({ dataIndex }) => colors[dataIndex] ?? fallback;
|
|
26
|
+
}
|
|
27
|
+
function checkSize(size) {
|
|
28
|
+
if (!(typeof size === "number" && Number.isFinite(size) && size > 0))
|
|
29
|
+
throw new InputError(`size is an image height in px > 0, got ${String(size)}`);
|
|
30
|
+
if (size < 1)
|
|
31
|
+
throw new InputError(
|
|
32
|
+
`size is a height in px, not a fraction of the plot (echarts' \`height\`): got ${size}, which draws a sub-pixel image`
|
|
33
|
+
);
|
|
34
|
+
return size;
|
|
35
|
+
}
|
|
36
|
+
var px = (v) => {
|
|
37
|
+
const n = typeof v === "string" ? Number(v) : v;
|
|
38
|
+
return typeof n === "number" && Number.isFinite(n) ? n : void 0;
|
|
39
|
+
};
|
|
40
|
+
function passThrough(props) {
|
|
41
|
+
return Object.fromEntries(
|
|
42
|
+
Object.entries(props).filter(
|
|
43
|
+
([k, v]) => v !== void 0 && (/^on[A-Z]/.test(k) || k.startsWith("data-") || k === "className" || k === "ref")
|
|
44
|
+
)
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
function image(p, kind, label, box, at, embed, extra) {
|
|
48
|
+
return createElement("image", {
|
|
49
|
+
...extra,
|
|
50
|
+
href: imageSource(p, embed),
|
|
51
|
+
x: box.left,
|
|
52
|
+
y: box.top,
|
|
53
|
+
width: box.h * aspect(p),
|
|
54
|
+
height: box.h,
|
|
55
|
+
preserveAspectRatio: "xMidYMid meet",
|
|
56
|
+
role: "img",
|
|
57
|
+
"aria-label": `${label} ${kind}`,
|
|
58
|
+
"data-sdv-id": p.id,
|
|
59
|
+
"data-sdv-kind": kind,
|
|
60
|
+
"data-sdv-x": String(at.x),
|
|
61
|
+
"data-sdv-y": String(at.y),
|
|
62
|
+
"data-sdv-frame": "1"
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
var teamPlace = (x, y, raw, o) => placeSync([x], [y], [raw], {
|
|
66
|
+
league: o.league,
|
|
67
|
+
kind: o.markType ?? "logo",
|
|
68
|
+
...o.season === void 0 ? {} : { season: o.season },
|
|
69
|
+
...o.variant === void 0 ? {} : { variant: o.variant },
|
|
70
|
+
...o.idSystem === void 0 ? {} : { idSystem: o.idSystem }
|
|
71
|
+
})[0];
|
|
72
|
+
var valueAt = (values, seriesId, i) => (Array.isArray(values) ? values : values[seriesId])?.[i];
|
|
73
|
+
function marker(name, kind, values, o, placed, league) {
|
|
74
|
+
const alpha = checkAlpha(o.alpha ?? 1);
|
|
75
|
+
return (props) => {
|
|
76
|
+
const { seriesId, dataIndex, x, y, size } = props;
|
|
77
|
+
const raw = valueAt(values, seriesId, dataIndex);
|
|
78
|
+
if (raw === void 0 || raw === null) {
|
|
79
|
+
warn(
|
|
80
|
+
`mui:${name}:no-value:${seriesId}`,
|
|
81
|
+
`${name}: series ${seriesId} has no ${kind === "headshot" ? "player" : "team"} for point ${dataIndex}: nothing drawn there`
|
|
82
|
+
);
|
|
83
|
+
return null;
|
|
84
|
+
}
|
|
85
|
+
if (!(size > 0)) {
|
|
86
|
+
warn(`mui:${name}:size:${size}`, `${name}: MUI drew a marker of size ${size} (px): nothing drawn`);
|
|
87
|
+
return null;
|
|
88
|
+
}
|
|
89
|
+
const p = placed(x, y, raw);
|
|
90
|
+
if (p === void 0) return null;
|
|
91
|
+
const h = 2 * size * (props.isHighlighted ? 1.2 : 1);
|
|
92
|
+
const w = h * aspect(p);
|
|
93
|
+
const opacity = alpha * (props.isFaded ? 0.3 : 1);
|
|
94
|
+
return image(
|
|
95
|
+
p,
|
|
96
|
+
kind,
|
|
97
|
+
placedName(p, league, raw),
|
|
98
|
+
{ left: x - w / 2, top: y - h / 2, h },
|
|
99
|
+
{ x, y },
|
|
100
|
+
o.embed,
|
|
101
|
+
{ ...passThrough(props), ...opacity === 1 ? {} : { opacity } }
|
|
102
|
+
);
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
function logoMarker(o) {
|
|
106
|
+
const kind = o.markType ?? "logo";
|
|
107
|
+
return marker("logoMarker", kind, o.teams, o, (x, y, raw) => teamPlace(x, y, raw, o), o.league);
|
|
108
|
+
}
|
|
109
|
+
function headshotMarker(o) {
|
|
110
|
+
const idSystem = o.idSystem ?? "espn";
|
|
111
|
+
return marker(
|
|
112
|
+
"headshotMarker",
|
|
113
|
+
"headshot",
|
|
114
|
+
o.players,
|
|
115
|
+
o,
|
|
116
|
+
(x, y, raw) => placeSync([x], [y], [raw], { league: o.league, kind: "headshot", idSystem })[0],
|
|
117
|
+
o.league
|
|
118
|
+
);
|
|
119
|
+
}
|
|
120
|
+
function logoMark(o) {
|
|
121
|
+
const h = checkSize(o.size ?? 24);
|
|
122
|
+
const alpha = checkAlpha(o.alpha ?? 1);
|
|
123
|
+
const kind = o.markType ?? "logo";
|
|
124
|
+
return (props) => {
|
|
125
|
+
const x = px(props.x);
|
|
126
|
+
const y = px(props.y);
|
|
127
|
+
if (x === void 0 || y === void 0 || props.hidden) return null;
|
|
128
|
+
const raw = o.teams === void 0 ? props.seriesId : o.teams[props.seriesId];
|
|
129
|
+
if (raw === void 0 || raw === null) {
|
|
130
|
+
warn(
|
|
131
|
+
`mui:mark:no-team:${props.seriesId}`,
|
|
132
|
+
`logoMark: no team for series ${props.seriesId} in teams: nothing drawn`
|
|
133
|
+
);
|
|
134
|
+
return null;
|
|
135
|
+
}
|
|
136
|
+
const p = teamPlace(x, y, raw, o);
|
|
137
|
+
if (p === void 0) return null;
|
|
138
|
+
const w = h * aspect(p);
|
|
139
|
+
const opacity = alpha * (props.isFaded ? 0.3 : 1);
|
|
140
|
+
return image(
|
|
141
|
+
p,
|
|
142
|
+
kind,
|
|
143
|
+
placedName(p, o.league, raw),
|
|
144
|
+
{ left: x - w / 2, top: y - h / 2, h },
|
|
145
|
+
{ x, y },
|
|
146
|
+
o.embed,
|
|
147
|
+
{ ...passThrough(props), ...opacity === 1 ? {} : { opacity } }
|
|
148
|
+
);
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
function anchored(x, y, w, h, style) {
|
|
152
|
+
const a = style?.textAnchor ?? "start";
|
|
153
|
+
const b = style?.dominantBaseline ?? "auto";
|
|
154
|
+
const left = a === "middle" ? x - w / 2 : a === "end" ? x - w : x;
|
|
155
|
+
const top = /^(hanging|text-before-edge)$/.test(b) ? y : /^(central|middle)$/.test(b) ? y - h / 2 : y - h;
|
|
156
|
+
return { left, top };
|
|
157
|
+
}
|
|
158
|
+
function logoTickLabel(o) {
|
|
159
|
+
const h = checkSize(o.size ?? 24);
|
|
160
|
+
const kind = o.markType ?? "logo";
|
|
161
|
+
return (props) => {
|
|
162
|
+
const x = px(props.x) ?? 0;
|
|
163
|
+
const y = px(props.y) ?? 0;
|
|
164
|
+
const text = props.text ?? "";
|
|
165
|
+
const p = text === "" ? void 0 : teamPlace(x, y, text, o);
|
|
166
|
+
if (p === void 0) {
|
|
167
|
+
const { textAnchor, dominantBaseline, ...style } = props.style ?? {};
|
|
168
|
+
return createElement(
|
|
169
|
+
"text",
|
|
170
|
+
{ x, y, className: props.className, textAnchor, dominantBaseline, style },
|
|
171
|
+
text
|
|
172
|
+
);
|
|
173
|
+
}
|
|
174
|
+
const w = h * aspect(p);
|
|
175
|
+
const box = anchored(x, y, w, h, props.style);
|
|
176
|
+
return image(p, kind, placedName(p, o.league, text), { ...box, h }, { x, y }, o.embed, {
|
|
177
|
+
className: props.className
|
|
178
|
+
});
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
export { headshotMarker, logoMark, logoMarker, logoTickLabel, teamColorGetter, teamColorPalette, teamColors };
|
|
183
|
+
//# sourceMappingURL=mui.js.map
|
|
184
|
+
//# sourceMappingURL=mui.js.map
|
package/dist/mui.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/mui.ts"],"names":[],"mappings":";;;;;;;;AAoDO,SAAS,gBAAA,CACd,MAAA,EACA,KAAA,EACA,CAAA,GAAqB,EAAC,EACZ;AACV,EAAA,OAAO,YAAA,CAAa,MAAA,EAAQ,KAAA,EAAO,CAAC,CAAA;AACtC;AAwCO,SAAS,UAAA,CACd,MAAA,EACA,KAAA,EACA,CAAA,GAAuB,EAAC,EACP;AACjB,EAAA,MAAM,EAAE,UAAA,EAAY,GAAG,EAAA,EAAG,GAAI,CAAA;AAC9B,EAAA,MAAM,EAAA,GAAK,EAAE,KAAA,EAAO,UAAA,EAAY,SAAS,SAAA,EAAW,IAAA,EAAM,UAAA,EAAY,IAAA,IAAQ,SAAA,EAAU;AACxF,EAAA,MAAM,OAA8C,EAAC;AACrD,EAAA,OAAO,CAAC,IAAA,KAAS;AACf,IAAA,MAAM,CAAA,GAAI,IAAA,KAAS,MAAA,GAAS,MAAA,GAAS,OAAA;AACrC,IAAA,IAAA,CAAK,CAAC,CAAA,KAAM,YAAA,CAAa,MAAA,EAAQ,KAAA,EAAO,EAAE,GAAG,EAAA,EAAI,UAAA,EAAY,EAAA,CAAG,CAAC,CAAA,EAAG,CAAA;AACpE,IAAA,OAAO,CAAC,GAAG,IAAA,CAAK,CAAC,CAAC,CAAA;AAAA,EACpB,CAAA;AACF;AA4BO,SAAS,eAAA,CACd,MAAA,EACA,KAAA,EACA,CAAA,GAAqB,EAAC,EACgB;AACtC,EAAA,MAAM,MAAA,GAAS,YAAA,CAAa,MAAA,EAAQ,KAAA,EAAO,CAAC,CAAA;AAC5C,EAAA,MAAM,QAAA,GAAW,EAAE,QAAA,IAAY,SAAA;AAC/B,EAAA,OAAO,CAAC,EAAE,SAAA,EAAU,KAAM,MAAA,CAAO,SAAS,CAAA,IAAK,QAAA;AACjD;AA4GA,SAAS,UAAU,IAAA,EAAuB;AACxC,EAAA,IAAI,EAAE,OAAO,IAAA,KAAS,QAAA,IAAY,OAAO,QAAA,CAAS,IAAI,KAAK,IAAA,GAAO,CAAA,CAAA;AAChE,IAAA,MAAM,IAAI,UAAA,CAAW,CAAA,uCAAA,EAA0C,MAAA,CAAO,IAAI,CAAC,CAAA,CAAE,CAAA;AAC/E,EAAA,IAAI,IAAA,GAAO,CAAA;AACT,IAAA,MAAM,IAAI,UAAA;AAAA,MACR,iFAAiF,IAAI,CAAA,+BAAA;AAAA,KACvF;AACF,EAAA,OAAO,IAAA;AACT;AACA,IAAM,EAAA,GAAK,CAAC,CAAA,KAAmC;AAC7C,EAAA,MAAM,IAAI,OAAO,CAAA,KAAM,QAAA,GAAW,MAAA,CAAO,CAAC,CAAA,GAAI,CAAA;AAC9C,EAAA,OAAO,OAAO,CAAA,KAAM,QAAA,IAAY,OAAO,QAAA,CAAS,CAAC,IAAI,CAAA,GAAI,MAAA;AAC3D,CAAA;AAEA,SAAS,YAAY,KAAA,EAAwC;AAC3D,EAAA,OAAO,MAAA,CAAO,WAAA;AAAA,IACZ,MAAA,CAAO,OAAA,CAAQ,KAAK,CAAA,CAAE,MAAA;AAAA,MACpB,CAAC,CAAC,CAAA,EAAG,CAAC,CAAA,KACJ,MAAM,MAAA,KAAc,UAAA,CAAW,IAAA,CAAK,CAAC,KAAK,CAAA,CAAE,UAAA,CAAW,OAAO,CAAA,IAAK,CAAA,KAAM,eAAe,CAAA,KAAM,KAAA;AAAA;AAClG,GACF;AACF;AAIA,SAAS,MACP,CAAA,EACA,IAAA,EACA,OACA,GAAA,EACA,EAAA,EACA,OACA,KAAA,EACc;AACd,EAAA,OAAO,cAAc,OAAA,EAAS;AAAA,IAC5B,GAAG,KAAA;AAAA,IACH,IAAA,EAAM,WAAA,CAAY,CAAA,EAAG,KAAK,CAAA;AAAA,IAC1B,GAAG,GAAA,CAAI,IAAA;AAAA,IACP,GAAG,GAAA,CAAI,GAAA;AAAA,IACP,KAAA,EAAO,GAAA,CAAI,CAAA,GAAI,MAAA,CAAO,CAAC,CAAA;AAAA,IACvB,QAAQ,GAAA,CAAI,CAAA;AAAA,IACZ,mBAAA,EAAqB,eAAA;AAAA,IACrB,IAAA,EAAM,KAAA;AAAA,IACN,YAAA,EAAc,CAAA,EAAG,KAAK,CAAA,CAAA,EAAI,IAAI,CAAA,CAAA;AAAA,IAC9B,eAAe,CAAA,CAAE,EAAA;AAAA,IACjB,eAAA,EAAiB,IAAA;AAAA,IACjB,YAAA,EAAc,MAAA,CAAO,EAAA,CAAG,CAAC,CAAA;AAAA,IACzB,YAAA,EAAc,MAAA,CAAO,EAAA,CAAG,CAAC,CAAA;AAAA,IACzB,gBAAA,EAAkB;AAAA,GACnB,CAAA;AACH;AAEA,IAAM,SAAA,GAAY,CAAC,CAAA,EAAW,CAAA,EAAW,KAAY,CAAA,KACnD,SAAA,CAAM,CAAC,CAAC,GAAG,CAAC,CAAC,CAAA,EAAG,CAAC,GAAG,CAAA,EAAG;AAAA,EACrB,QAAQ,CAAA,CAAE,MAAA;AAAA,EACV,IAAA,EAAM,EAAE,QAAA,IAAY,MAAA;AAAA,EACpB,GAAI,EAAE,MAAA,KAAW,MAAA,GAAY,EAAC,GAAI,EAAE,MAAA,EAAQ,CAAA,CAAE,MAAA,EAAO;AAAA,EACrD,GAAI,EAAE,OAAA,KAAY,MAAA,GAAY,EAAC,GAAI,EAAE,OAAA,EAAS,CAAA,CAAE,OAAA,EAAQ;AAAA,EACxD,GAAI,EAAE,QAAA,KAAa,MAAA,GAAY,EAAC,GAAI,EAAE,QAAA,EAAU,CAAA,CAAE,QAAA;AACpD,CAAC,EAAE,CAAC,CAAA;AAEN,IAAM,OAAA,GAAU,CAAC,MAAA,EAAwB,QAAA,EAAkB,CAAA,KAAA,CACxD,KAAA,CAAM,OAAA,CAAQ,MAAM,CAAA,GAAI,MAAA,GAAU,MAAA,CAA4C,QAAQ,KAAK,CAAC,CAAA;AAE/F,SAAS,OACP,IAAA,EACA,IAAA,EACA,MAAA,EACA,CAAA,EACA,QACA,MAAA,EACe;AACf,EAAA,MAAM,KAAA,GAAQ,UAAA,CAAW,CAAA,CAAE,KAAA,IAAS,CAAC,CAAA;AACrC,EAAA,OAAO,CAAC,KAAA,KAAU;AAChB,IAAA,MAAM,EAAE,QAAA,EAAU,SAAA,EAAW,CAAA,EAAG,CAAA,EAAG,MAAK,GAAI,KAAA;AAC5C,IAAA,MAAM,GAAA,GAAM,OAAA,CAAQ,MAAA,EAAQ,QAAA,EAAU,SAAS,CAAA;AAC/C,IAAA,IAAI,GAAA,KAAQ,MAAA,IAAa,GAAA,KAAQ,IAAA,EAAM;AACrC,MAAA,IAAA;AAAA,QACE,CAAA,IAAA,EAAO,IAAI,CAAA,UAAA,EAAa,QAAQ,CAAA,CAAA;AAAA,QAChC,CAAA,EAAG,IAAI,CAAA,SAAA,EAAY,QAAQ,CAAA,QAAA,EAAW,SAAS,UAAA,GAAa,QAAA,GAAW,MAAM,CAAA,WAAA,EAAc,SAAS,CAAA,qBAAA;AAAA,OACtG;AACA,MAAA,OAAO,IAAA;AAAA,IACT;AACA,IAAA,IAAI,EAAE,OAAO,CAAA,CAAA,EAAI;AACf,MAAA,IAAA,CAAK,CAAA,IAAA,EAAO,IAAI,CAAA,MAAA,EAAS,IAAI,IAAI,CAAA,EAAG,IAAI,CAAA,4BAAA,EAA+B,IAAI,CAAA,oBAAA,CAAsB,CAAA;AACjG,MAAA,OAAO,IAAA;AAAA,IACT;AACA,IAAA,MAAM,CAAA,GAAI,MAAA,CAAO,CAAA,EAAG,CAAA,EAAG,GAAG,CAAA;AAC1B,IAAA,IAAI,CAAA,KAAM,QAAW,OAAO,IAAA;AAE5B,IAAA,MAAM,CAAA,GAAI,CAAA,GAAI,IAAA,IAAQ,KAAA,CAAM,gBAAgB,GAAA,GAAM,CAAA,CAAA;AAClD,IAAA,MAAM,CAAA,GAAI,CAAA,GAAI,MAAA,CAAO,CAAC,CAAA;AACtB,IAAA,MAAM,OAAA,GAAU,KAAA,IAAS,KAAA,CAAM,OAAA,GAAU,GAAA,GAAM,CAAA,CAAA;AAC/C,IAAA,OAAO,KAAA;AAAA,MACL,CAAA;AAAA,MACA,IAAA;AAAA,MACA,UAAA,CAAW,CAAA,EAAG,MAAA,EAAQ,GAAG,CAAA;AAAA,MACzB,EAAE,MAAM,CAAA,GAAI,CAAA,GAAI,GAAG,GAAA,EAAK,CAAA,GAAI,CAAA,GAAI,CAAA,EAAG,CAAA,EAAE;AAAA,MACrC,EAAE,GAAG,CAAA,EAAE;AAAA,MACP,CAAA,CAAE,KAAA;AAAA,MACF,EAAE,GAAG,WAAA,CAAY,KAAK,CAAA,EAAG,GAAI,OAAA,KAAY,CAAA,GAAI,EAAC,GAAI,EAAE,OAAA,EAAQ;AAAG,KACjE;AAAA,EACF,CAAA;AACF;AA+BO,SAAS,WAAW,CAAA,EAAoC;AAC7D,EAAA,MAAM,IAAA,GAAO,EAAE,QAAA,IAAY,MAAA;AAC3B,EAAA,OAAO,OAAO,YAAA,EAAc,IAAA,EAAM,CAAA,CAAE,KAAA,EAAO,GAAG,CAAC,CAAA,EAAG,CAAA,EAAG,GAAA,KAAQ,UAAU,CAAA,EAAG,CAAA,EAAG,KAAK,CAAC,CAAA,EAAG,EAAE,MAAM,CAAA;AAChG;AAoBO,SAAS,eAAe,CAAA,EAAsC;AACnE,EAAA,MAAM,QAAA,GAAW,EAAE,QAAA,IAAY,MAAA;AAC/B,EAAA,OAAO,MAAA;AAAA,IACL,gBAAA;AAAA,IACA,UAAA;AAAA,IACA,CAAA,CAAE,OAAA;AAAA,IACF,CAAA;AAAA,IACA,CAAC,GAAG,CAAA,EAAG,GAAA,KAAQ,UAAM,CAAC,CAAC,CAAA,EAAG,CAAC,CAAC,CAAA,EAAG,CAAC,GAAG,CAAA,EAAG,EAAE,MAAA,EAAQ,CAAA,CAAE,MAAA,EAAQ,MAAM,UAAA,EAAY,QAAA,EAAU,CAAA,CAAE,CAAC,CAAA;AAAA,IACzF,CAAA,CAAE;AAAA,GACJ;AACF;AA4BO,SAAS,SAAS,CAAA,EAAgC;AACvD,EAAA,MAAM,CAAA,GAAI,SAAA,CAAU,CAAA,CAAE,IAAA,IAAQ,EAAE,CAAA;AAChC,EAAA,MAAM,KAAA,GAAQ,UAAA,CAAW,CAAA,CAAE,KAAA,IAAS,CAAC,CAAA;AACrC,EAAA,MAAM,IAAA,GAAO,EAAE,QAAA,IAAY,MAAA;AAC3B,EAAA,OAAO,CAAC,KAAA,KAAU;AAChB,IAAA,MAAM,CAAA,GAAI,EAAA,CAAG,KAAA,CAAM,CAAC,CAAA;AACpB,IAAA,MAAM,CAAA,GAAI,EAAA,CAAG,KAAA,CAAM,CAAC,CAAA;AACpB,IAAA,IAAI,MAAM,MAAA,IAAa,CAAA,KAAM,MAAA,IAAa,KAAA,CAAM,QAAQ,OAAO,IAAA;AAC/D,IAAA,MAAM,GAAA,GAAM,EAAE,KAAA,KAAU,MAAA,GAAY,MAAM,QAAA,GAAW,CAAA,CAAE,KAAA,CAAM,KAAA,CAAM,QAAQ,CAAA;AAC3E,IAAA,IAAI,GAAA,KAAQ,MAAA,IAAa,GAAA,KAAQ,IAAA,EAAM;AACrC,MAAA,IAAA;AAAA,QACE,CAAA,iBAAA,EAAoB,MAAM,QAAQ,CAAA,CAAA;AAAA,QAClC,CAAA,6BAAA,EAAgC,MAAM,QAAQ,CAAA,wBAAA;AAAA,OAChD;AACA,MAAA,OAAO,IAAA;AAAA,IACT;AACA,IAAA,MAAM,CAAA,GAAI,SAAA,CAAU,CAAA,EAAG,CAAA,EAAG,KAAK,CAAC,CAAA;AAChC,IAAA,IAAI,CAAA,KAAM,QAAW,OAAO,IAAA;AAC5B,IAAA,MAAM,CAAA,GAAI,CAAA,GAAI,MAAA,CAAO,CAAC,CAAA;AACtB,IAAA,MAAM,OAAA,GAAU,KAAA,IAAS,KAAA,CAAM,OAAA,GAAU,GAAA,GAAM,CAAA,CAAA;AAC/C,IAAA,OAAO,KAAA;AAAA,MACL,CAAA;AAAA,MACA,IAAA;AAAA,MACA,UAAA,CAAW,CAAA,EAAG,CAAA,CAAE,MAAA,EAAQ,GAAG,CAAA;AAAA,MAC3B,EAAE,MAAM,CAAA,GAAI,CAAA,GAAI,GAAG,GAAA,EAAK,CAAA,GAAI,CAAA,GAAI,CAAA,EAAG,CAAA,EAAE;AAAA,MACrC,EAAE,GAAG,CAAA,EAAE;AAAA,MACP,CAAA,CAAE,KAAA;AAAA,MACF,EAAE,GAAG,WAAA,CAAY,KAAK,CAAA,EAAG,GAAI,OAAA,KAAY,CAAA,GAAI,EAAC,GAAI,EAAE,OAAA,EAAQ;AAAG,KACjE;AAAA,EACF,CAAA;AACF;AAGA,SAAS,QAAA,CAAS,CAAA,EAAW,CAAA,EAAW,CAAA,EAAW,GAAW,KAAA,EAAmC;AAC/F,EAAA,MAAM,CAAA,GAAI,OAAO,UAAA,IAAc,OAAA;AAC/B,EAAA,MAAM,CAAA,GAAI,OAAO,gBAAA,IAAoB,MAAA;AACrC,EAAA,MAAM,IAAA,GAAO,MAAM,QAAA,GAAW,CAAA,GAAI,IAAI,CAAA,GAAI,CAAA,KAAM,KAAA,GAAQ,CAAA,GAAI,CAAA,GAAI,CAAA;AAChE,EAAA,MAAM,GAAA,GAAM,8BAAA,CAA+B,IAAA,CAAK,CAAC,CAAA,GAAI,CAAA,GAAI,oBAAA,CAAqB,IAAA,CAAK,CAAC,CAAA,GAAI,CAAA,GAAI,CAAA,GAAI,IAAI,CAAA,GAAI,CAAA;AACxG,EAAA,OAAO,EAAE,MAAM,GAAA,EAAI;AACrB;AAqCO,SAAS,cAAc,CAAA,EAA0C;AACtE,EAAA,MAAM,CAAA,GAAI,SAAA,CAAU,CAAA,CAAE,IAAA,IAAQ,EAAE,CAAA;AAChC,EAAA,MAAM,IAAA,GAAO,EAAE,QAAA,IAAY,MAAA;AAC3B,EAAA,OAAO,CAAC,KAAA,KAAU;AAChB,IAAA,MAAM,CAAA,GAAI,EAAA,CAAG,KAAA,CAAM,CAAC,CAAA,IAAK,CAAA;AACzB,IAAA,MAAM,CAAA,GAAI,EAAA,CAAG,KAAA,CAAM,CAAC,CAAA,IAAK,CAAA;AACzB,IAAA,MAAM,IAAA,GAAO,MAAM,IAAA,IAAQ,EAAA;AAC3B,IAAA,MAAM,CAAA,GAAI,SAAS,EAAA,GAAK,MAAA,GAAY,UAAU,CAAA,EAAG,CAAA,EAAG,MAAM,CAAC,CAAA;AAC3D,IAAA,IAAI,MAAM,MAAA,EAAW;AAEnB,MAAA,MAAM,EAAE,YAAY,gBAAA,EAAkB,GAAG,OAAM,GAAI,KAAA,CAAM,SAAS,EAAC;AACnE,MAAA,OAAO,aAAA;AAAA,QACL,MAAA;AAAA,QACA,EAAE,GAAG,CAAA,EAAG,SAAA,EAAW,MAAM,SAAA,EAAW,UAAA,EAAY,kBAAkB,KAAA,EAAM;AAAA,QACxE;AAAA,OACF;AAAA,IACF;AACA,IAAA,MAAM,CAAA,GAAI,CAAA,GAAI,MAAA,CAAO,CAAC,CAAA;AACtB,IAAA,MAAM,MAAM,QAAA,CAAS,CAAA,EAAG,GAAG,CAAA,EAAG,CAAA,EAAG,MAAM,KAAK,CAAA;AAC5C,IAAA,OAAO,MAAM,CAAA,EAAG,IAAA,EAAM,WAAW,CAAA,EAAG,CAAA,CAAE,QAAQ,IAAI,CAAA,EAAG,EAAE,GAAG,GAAA,EAAK,GAAE,EAAG,EAAE,GAAG,CAAA,EAAE,EAAG,EAAE,KAAA,EAAO;AAAA,MACrF,WAAW,KAAA,CAAM;AAAA,KAClB,CAAA;AAAA,EACH,CAAA;AACF","file":"mui.js","sourcesContent":["/**\n * The MUI X Charts adapter (`@mui/x-charts` 9, the MIT community package): team colours for a chart's `colors` (a\n * palette that follows MUI's light and dark mode) or a series' `colorGetter` (a colour per data point), and team logos\n * and player headshots through MUI's own slots: a scatter's `marker`, a line's `mark` and an axis' `axisTickLabel`.\n * Pure: MUI is never imported (the slot props here are structural, read from `@mui/x-charts` 9.15 and checked against\n * its own types); React's `createElement` builds the SVG `<image>` each slot returns, so the logos also render on the\n * server. Call after `loadLeague(league)` / `preloadAll()`. `sdvplot/testing`'s `drawnMarks(container)` reads back\n * what the slots drew.\n */\nimport { type ReactElement, createElement } from \"react\";\nimport { aspect, checkAlpha, colorPalette, imageSource, place } from \"./_web.js\";\nimport { InputError, warn } from \"./errors.js\";\nimport type { EspnHeadshotLeague } from \"./headshots.js\";\nimport { type Placement, placedName } from \"./placement.js\";\nimport type { Value } from \"./resolve.js\";\nimport type { HeadshotIdSystem, IdSystem, League, MarkType, SeasonInput, Variant, Which } from \"./types.js\";\nexport { embedSources } from \"./_web.js\"; // public: build `embed` with it, as for the other adapters\n\n/** `teamColorPalette` and `teamColorGetter` options. */\nexport interface MuiColorOptions {\n /** \"primary\" (default) or \"secondary\" colour. */\n which?: Which;\n /** The season the teams are resolved for (a number or string). */\n season?: SeasonInput;\n /** How the team values are read. Default \"auto\". */\n idSystem?: IdSystem;\n /** The colour of an unresolved team. Default \"#808080\". */\n fallback?: string;\n /**\n * The chart's background. Given, a team colour under 3:1 contrast against it is swapped for the team's other\n * colour, else moved towards white (on a dark background) or black (a light one) until it reads: dark mode. A\n * colour that would then sit too close to an earlier team's (CIE76 ΔE under 20) takes the team's other colour, or\n * the nearest colour to its own (lightness, hue or chroma stepped) that is distinct and still reads; failing that it\n * is kept, with one warning.\n */\n background?: string;\n}\n\n/**\n * One colour per team, in order: a chart's `colors` (series i takes colour i) or a series' `color`. With\n * `background`, every colour keeps 3:1 contrast against it (dark mode) and aims to stay distinct from the others.\n * `teamColors` gives the same colours as a palette that follows MUI's light and dark mode.\n *\n * @example\n * ```tsx\n * import { loadLeague } from \"@sportsdataverse/sdvplot\";\n * import { teamColorPalette } from \"@sportsdataverse/sdvplot/mui\";\n *\n * await loadLeague(\"nfl\");\n * teamColorPalette(\"nfl\", [\"KC\", \"LAC\", \"DEN\", \"LV\"], { background: \"#121212\" });\n * ```\n */\nexport function teamColorPalette(\n league: League,\n teams: readonly unknown[],\n o: MuiColorOptions = {},\n): string[] {\n return colorPalette(league, teams, o);\n}\n\n/** `teamColors` options: the backgrounds the light and dark palettes keep 3:1 contrast against. */\nexport interface MuiPaletteOptions extends Omit<MuiColorOptions, \"background\"> {\n /** The chart's background in each mode. Default MUI's own: `{ light: \"#ffffff\", dark: \"#121212\" }`. */\n background?: { light?: string; dark?: string };\n}\n/** MUI's `colors` callback: the palette for the theme's mode. */\nexport type MuiColorPalette = (mode: \"light\" | \"dark\") => string[];\n\n/**\n * Team colours as a chart's `colors`, MUI's palette callback: MUI calls it with the theme's mode, so under a dark\n * `ThemeProvider` every series takes its team colour readable on the dark background (3:1 contrast, distinct from the\n * others), and under a light one readable on white. Series i takes `teams[i]`'s colour: give the series in the same\n * order. Each mode's palette is worked out once, when MUI first asks for it, so build it once (module scope or\n * `useMemo`): a new one on every render works the palettes out again.\n *\n * @example\n * ```tsx\n * import { createTheme, ThemeProvider } from \"@mui/material/styles\";\n * import { LineChart } from \"@mui/x-charts/LineChart\";\n * import { loadLeague } from \"@sportsdataverse/sdvplot\";\n * import { teamColors } from \"@sportsdataverse/sdvplot/mui\";\n *\n * await loadLeague(\"nfl\");\n * const teams = [\"KC\", \"DEN\"];\n * <ThemeProvider theme={createTheme({ palette: { mode: \"dark\" } })}>\n * <LineChart\n * width={400}\n * height={240}\n * colors={teamColors(\"nfl\", teams)}\n * xAxis={[{ data: [1, 2, 3] }]}\n * series={[\n * { id: \"KC\", data: [1, 2, 3] },\n * { id: \"DEN\", data: [0, 1, 2] },\n * ]}\n * />\n * </ThemeProvider>;\n * ```\n */\nexport function teamColors(\n league: League,\n teams: readonly unknown[],\n o: MuiPaletteOptions = {},\n): MuiColorPalette {\n const { background, ...co } = o;\n const bg = { light: background?.light ?? \"#ffffff\", dark: background?.dark ?? \"#121212\" };\n const memo: { light?: string[]; dark?: string[] } = {};\n return (mode) => {\n const m = mode === \"dark\" ? \"dark\" : \"light\";\n memo[m] ??= colorPalette(league, teams, { ...co, background: bg[m] });\n return [...memo[m]];\n };\n}\n\n/** What MUI hands a series' `colorGetter`: the data point's index (and value, unused here). */\nexport interface MuiColorCallbackValue {\n /** The data point's index in its series. */\n dataIndex: number;\n}\n/**\n * A colour per data point, for a series' `colorGetter` (bar, scatter, line): `teams[i]` is the team of data point\n * i, so each bar or point takes its own team's colour. A team that repeats keeps one colour. With `background`, every\n * colour keeps 3:1 contrast against it (dark mode). A point past the end of `teams` takes `fallback`.\n *\n * @example\n * ```tsx\n * import { BarChart } from \"@mui/x-charts/BarChart\";\n * import { loadLeague } from \"@sportsdataverse/sdvplot\";\n * import { teamColorGetter } from \"@sportsdataverse/sdvplot/mui\";\n *\n * await loadLeague(\"nfl\");\n * const teams = [\"KC\", \"BUF\", \"BAL\"];\n * <BarChart\n * width={400}\n * height={240}\n * xAxis={[{ scaleType: \"band\", data: teams }]}\n * series={[{ data: [15, 13, 12], colorGetter: teamColorGetter(\"nfl\", teams) }]}\n * />;\n * ```\n */\nexport function teamColorGetter(\n league: League,\n teams: readonly unknown[],\n o: MuiColorOptions = {},\n): (d: MuiColorCallbackValue) => string {\n const colors = colorPalette(league, teams, o);\n const fallback = o.fallback ?? \"#808080\";\n return ({ dataIndex }) => colors[dataIndex] ?? fallback;\n}\n\n/** What MUI hands a scatter's `marker` slot: the subset read here. */\nexport interface MuiMarkerProps {\n /** The series the point belongs to. */\n seriesId: string;\n /** The point's index in its series' data. */\n dataIndex: number;\n /** The point's centre in px. */\n x: number;\n /** The point's centre in px. */\n y: number;\n /** The marker's radius in px: the series' `markerSize`. The logo is twice it tall. */\n size: number;\n /** MUI highlights the point (the pointer is on it or its series): drawn 1.2 times as tall. */\n isHighlighted?: boolean;\n /** MUI fades the point (another series is highlighted): drawn at 0.3 of the opacity. */\n isFaded?: boolean;\n /** MUI's click handler, kept on the image. */\n onClick?: ((event: never) => void) | undefined;\n}\n/** What MUI hands a line's `mark` slot: the subset read here. */\nexport interface MuiMarkProps {\n /** The series the mark belongs to: by default its id is the team. */\n seriesId: string;\n /** The mark's index along the x axis. */\n dataIndex: number;\n /** The mark's centre in px. */\n x?: number | string | undefined;\n /** The mark's centre in px. */\n y?: number | string | undefined;\n /** The series is hidden (from its legend): nothing is drawn. */\n hidden?: boolean | undefined;\n /** MUI highlights the mark; a logo keeps its size. */\n isHighlighted?: boolean | undefined;\n /** MUI fades the series (another is highlighted): drawn at 0.3 of the opacity. */\n isFaded?: boolean | undefined;\n /** MUI's click handler, kept on the image. */\n onClick?: ((event: never) => void) | undefined;\n}\n/** What MUI hands an axis' `axisTickLabel` slot: the subset read here. */\nexport interface MuiTickLabelProps {\n /** The label's anchor in px (in its tick's frame). */\n x?: number | string | undefined;\n /** The label's anchor in px (in its tick's frame). */\n y?: number | string | undefined;\n /** The tick's label: the team, as the axis' `valueFormatter` wrote it. */\n text?: string | undefined;\n /** The label's style: its `textAnchor` and `dominantBaseline` say which side of the anchor it runs. */\n style?: { textAnchor?: string | undefined; dominantBaseline?: string | undefined } | undefined;\n /** MUI's tick label class, kept on the image or the text. */\n className?: string | undefined;\n}\n/** A scatter's `marker` slot: `<ScatterChart slots={{ marker }}>`. */\nexport type MuiMarkerSlot = (props: MuiMarkerProps) => ReactElement | null;\n/** A line's `mark` slot: `<LineChart slots={{ mark }}>`. */\nexport type MuiMarkSlot = (props: MuiMarkProps) => ReactElement | null;\n/** An axis' `axisTickLabel` slot: `<ChartsXAxis slots={{ axisTickLabel }}>`. */\nexport type MuiTickLabelSlot = (props: MuiTickLabelProps) => ReactElement;\n\n/** The team (or player) of each data point: one list for every series, or a list per series id. */\nexport type MuiPointValues = readonly Value[] | Readonly<Record<string, readonly Value[]>>;\n\ninterface Shared {\n /** Opacity in [0, 1]. Default 1. MUI's faded points (another series highlighted) draw at 0.3 of it. */\n alpha?: number;\n /** url → data URI map from `embedSources`, to inline the images. */\n embed?: ReadonlyMap<string, string>;\n}\ninterface TeamShared extends Shared {\n /** The league the teams belong to, e.g. \"nfl\". */\n league: League;\n /** The season the teams are resolved for. */\n season?: SeasonInput;\n /** Logo variant, e.g. \"dark\". Default \"default\". */\n variant?: Variant;\n /** \"logo\" (default) or \"wordmark\". */\n markType?: MarkType;\n /** How the team values are read. Default \"auto\". */\n idSystem?: IdSystem;\n}\n/** `logoMarker` options. */\nexport interface MuiMarkerOptions extends TeamShared {\n /** The team of each data point (abbreviation, name or id; see `idSystem`): `teams[i]` is point i's. */\n teams: MuiPointValues;\n}\n/** `headshotMarker` options. */\nexport interface MuiHeadshotOptions extends Shared {\n /** The player of each data point: `players[i]` is point i's. */\n players: MuiPointValues;\n /** The league the players belong to, e.g. \"nfl\". */\n league: EspnHeadshotLeague;\n /** ESPN athlete ids, or nflverse gsis ids (needs `loadGsis()` first). Default \"espn\". */\n idSystem?: HeadshotIdSystem;\n}\n/** `logoMark` options. */\nexport interface MuiMarkOptions extends TeamShared {\n /** Each series' team by series id. Default: the series id is the team. */\n teams?: Readonly<Record<string, Value>>;\n /** The logo's height in px, 1 or more (MUI hands a line mark no size). Default 24. */\n size?: number;\n}\n/** `logoTickLabel` options. */\nexport interface MuiTickLabelOptions extends Omit<TeamShared, \"alpha\"> {\n /** The logo's height in px, 1 or more. Default 24: give the axis room (an x axis' `height`, a y axis' `width`). */\n size?: number;\n}\n\nfunction checkSize(size: unknown): number {\n if (!(typeof size === \"number\" && Number.isFinite(size) && size > 0))\n throw new InputError(`size is an image height in px > 0, got ${String(size)}`);\n if (size < 1)\n throw new InputError(\n `size is a height in px, not a fraction of the plot (echarts' \\`height\\`): got ${size}, which draws a sub-pixel image`,\n );\n return size;\n}\nconst px = (v: unknown): number | undefined => {\n const n = typeof v === \"string\" ? Number(v) : v;\n return typeof n === \"number\" && Number.isFinite(n) ? n : undefined;\n};\n/** The event handlers, class, ref and `data-*` MUI put on its own marker, kept on the image (tooltips, highlight). */\nfunction passThrough(props: object): Record<string, unknown> {\n return Object.fromEntries(\n Object.entries(props).filter(\n ([k, v]) =>\n v !== undefined && (/^on[A-Z]/.test(k) || k.startsWith(\"data-\") || k === \"className\" || k === \"ref\"),\n ),\n );\n}\n\n/** The `<image>` of a placed mark, `h` px tall with its top-left at (left, top), stamped with what `sdvplot/testing`'s\n * `drawnMarks` reads back. ponytail: the frame is 1 px, so drawnMarks' `height` reads in px (no plot height here). */\nfunction image(\n p: Placement,\n kind: string,\n label: string,\n box: { left: number; top: number; h: number },\n at: { x: number; y: number },\n embed: ReadonlyMap<string, string> | undefined,\n extra: Record<string, unknown>,\n): ReactElement {\n return createElement(\"image\", {\n ...extra,\n href: imageSource(p, embed),\n x: box.left,\n y: box.top,\n width: box.h * aspect(p),\n height: box.h,\n preserveAspectRatio: \"xMidYMid meet\",\n role: \"img\",\n \"aria-label\": `${label} ${kind}`,\n \"data-sdv-id\": p.id,\n \"data-sdv-kind\": kind,\n \"data-sdv-x\": String(at.x),\n \"data-sdv-y\": String(at.y),\n \"data-sdv-frame\": \"1\",\n });\n}\n\nconst teamPlace = (x: number, y: number, raw: Value, o: TeamShared): Placement | undefined =>\n place([x], [y], [raw], {\n league: o.league,\n kind: o.markType ?? \"logo\",\n ...(o.season === undefined ? {} : { season: o.season }),\n ...(o.variant === undefined ? {} : { variant: o.variant }),\n ...(o.idSystem === undefined ? {} : { idSystem: o.idSystem }),\n })[0];\n\nconst valueAt = (values: MuiPointValues, seriesId: string, i: number): Value | undefined =>\n (Array.isArray(values) ? values : (values as Record<string, readonly Value[]>)[seriesId])?.[i];\n\nfunction marker(\n name: string,\n kind: \"logo\" | \"wordmark\" | \"headshot\",\n values: MuiPointValues,\n o: Shared,\n placed: (x: number, y: number, raw: Value) => Placement | undefined,\n league: League,\n): MuiMarkerSlot {\n const alpha = checkAlpha(o.alpha ?? 1);\n return (props) => {\n const { seriesId, dataIndex, x, y, size } = props;\n const raw = valueAt(values, seriesId, dataIndex);\n if (raw === undefined || raw === null) {\n warn(\n `mui:${name}:no-value:${seriesId}`,\n `${name}: series ${seriesId} has no ${kind === \"headshot\" ? \"player\" : \"team\"} for point ${dataIndex}: nothing drawn there`,\n );\n return null;\n }\n if (!(size > 0)) {\n warn(`mui:${name}:size:${size}`, `${name}: MUI drew a marker of size ${size} (px): nothing drawn`);\n return null;\n }\n const p = placed(x, y, raw);\n if (p === undefined) return null; // no mark: nothing drawn, one warning (placeSync), as sdvplot/echarts\n // MUI's own marker grows 1.2 times when highlighted and fades to 0.3 when another series is highlighted\n const h = 2 * size * (props.isHighlighted ? 1.2 : 1);\n const w = h * aspect(p);\n const opacity = alpha * (props.isFaded ? 0.3 : 1);\n return image(\n p,\n kind,\n placedName(p, league, raw),\n { left: x - w / 2, top: y - h / 2, h },\n { x, y },\n o.embed,\n { ...passThrough(props), ...(opacity === 1 ? {} : { opacity }) },\n );\n };\n}\n\n/**\n * Team logos as a scatter's points: `<ScatterChart slots={{ marker: logoMarker({ league, teams }) }}>`. Point i is\n * drawn as the logo of `teams[i]` (or `teams[seriesId][i]`), centred on the point and twice the series'\n * `markerSize` tall, in px: set `markerSize` on the series. MUI's tooltip, highlight (1.2 times as tall) and fade\n * (0.3 opacity) keep working. A team with no logo draws nothing and warns once, as `sdvplot/echarts` does.\n * Synchronous: call it after `loadLeague(league)`. Build it once (module scope or `useMemo`): a new slot on every\n * render remounts the points.\n *\n * @example\n * ```tsx\n * import { ScatterChart } from \"@mui/x-charts/ScatterChart\";\n * import { loadLeague } from \"@sportsdataverse/sdvplot\";\n * import { logoMarker } from \"@sportsdataverse/sdvplot/mui\";\n *\n * await loadLeague(\"nfl\");\n * const rows = [\n * { team: \"KC\", pf: 385, pa: 326 },\n * { team: \"LAC\", pf: 402, pa: 301 },\n * { team: \"DEN\", pf: 425, pa: 311 },\n * ];\n * const marker = logoMarker({ league: \"nfl\", teams: rows.map((r) => r.team) });\n * <ScatterChart\n * width={400}\n * height={300}\n * series={[{ data: rows.map((r) => ({ x: r.pf, y: r.pa })), markerSize: 14 }]}\n * slots={{ marker }}\n * />;\n * ```\n */\nexport function logoMarker(o: MuiMarkerOptions): MuiMarkerSlot {\n const kind = o.markType ?? \"logo\";\n return marker(\"logoMarker\", kind, o.teams, o, (x, y, raw) => teamPlace(x, y, raw, o), o.league);\n}\n\n/**\n * ESPN player headshots as a scatter's points (`marker` slot), from `players[i]` for point i; twice the series'\n * `markerSize` tall. An id with no headshot draws nothing and warns once. `idSystem: \"gsis\"` needs `loadGsis()` first.\n *\n * @example\n * ```tsx\n * import { ScatterChart } from \"@mui/x-charts/ScatterChart\";\n * import { headshotMarker } from \"@sportsdataverse/sdvplot/mui\";\n *\n * // Patrick Mahomes (ESPN 3139477): 2024, 3,928 passing yards and 26 touchdowns\n * <ScatterChart\n * width={400}\n * height={300}\n * series={[{ data: [{ x: 3928, y: 26 }], markerSize: 20 }]}\n * slots={{ marker: headshotMarker({ league: \"nfl\", players: [\"3139477\"] }) }}\n * />;\n * ```\n */\nexport function headshotMarker(o: MuiHeadshotOptions): MuiMarkerSlot {\n const idSystem = o.idSystem ?? \"espn\";\n return marker(\n \"headshotMarker\",\n \"headshot\",\n o.players,\n o,\n (x, y, raw) => place([x], [y], [raw], { league: o.league, kind: \"headshot\", idSystem })[0],\n o.league,\n );\n}\n\n/**\n * Team logos as a line's marks: `<LineChart slots={{ mark: logoMark({ league }) }}>`. Each mark is drawn as its\n * series' team logo, `size` px tall and centred on the point: the series' id is the team (or give `teams`, series id\n * to team). Which points get a mark is the series' `showMark`: `({ index }) => index === last` puts one logo at the\n * end of each line. A team with no logo draws nothing and warns once. Build it once (module scope or `useMemo`).\n *\n * @example\n * ```tsx\n * import { LineChart } from \"@mui/x-charts/LineChart\";\n * import { loadLeague } from \"@sportsdataverse/sdvplot\";\n * import { logoMark } from \"@sportsdataverse/sdvplot/mui\";\n *\n * await loadLeague(\"nfl\");\n * const end = ({ index }: { index: number }) => index === 2; // the last of three points\n * <LineChart\n * width={400}\n * height={240}\n * xAxis={[{ data: [1, 2, 3] }]}\n * series={[\n * { id: \"KC\", data: [1, 2, 3], showMark: end },\n * { id: \"DEN\", data: [0, 1, 2], showMark: end },\n * ]}\n * slots={{ mark: logoMark({ league: \"nfl\", size: 28 }) }}\n * />;\n * ```\n */\nexport function logoMark(o: MuiMarkOptions): MuiMarkSlot {\n const h = checkSize(o.size ?? 24);\n const alpha = checkAlpha(o.alpha ?? 1);\n const kind = o.markType ?? \"logo\";\n return (props) => {\n const x = px(props.x);\n const y = px(props.y);\n if (x === undefined || y === undefined || props.hidden) return null;\n const raw = o.teams === undefined ? props.seriesId : o.teams[props.seriesId];\n if (raw === undefined || raw === null) {\n warn(\n `mui:mark:no-team:${props.seriesId}`,\n `logoMark: no team for series ${props.seriesId} in teams: nothing drawn`,\n );\n return null;\n }\n const p = teamPlace(x, y, raw, o);\n if (p === undefined) return null; // no mark: nothing drawn, one warning (placeSync)\n const w = h * aspect(p);\n const opacity = alpha * (props.isFaded ? 0.3 : 1);\n return image(\n p,\n kind,\n placedName(p, o.league, raw),\n { left: x - w / 2, top: y - h / 2, h },\n { x, y },\n o.embed,\n { ...passThrough(props), ...(opacity === 1 ? {} : { opacity }) },\n );\n };\n}\n\n/** Where a box of width w and height h starts, for a label anchored at (x, y) the way `style` would place text. */\nfunction anchored(x: number, y: number, w: number, h: number, style: MuiTickLabelProps[\"style\"]) {\n const a = style?.textAnchor ?? \"start\";\n const b = style?.dominantBaseline ?? \"auto\";\n const left = a === \"middle\" ? x - w / 2 : a === \"end\" ? x - w : x;\n const top = /^(hanging|text-before-edge)$/.test(b) ? y : /^(central|middle)$/.test(b) ? y - h / 2 : y - h; // alphabetic, auto, ideographic, text-after-edge: the text sits above the anchor\n return { left, top };\n}\n\n/**\n * Team logos as an axis' tick labels: `<ChartsXAxis slots={{ axisTickLabel: logoTickLabel({ league }) }} />` on a\n * band axis whose values are teams. The logo sits where MUI would put the label (under a bottom axis, left of a left\n * one), `size` px tall; give the axis room for it (an x axis' `height`, a y axis' `width`). A label that resolves to\n * no logo keeps its text and warns once, as `sdvplot/echarts`' `withAxisLogos` does. The label is the axis'\n * formatted value, so a `valueFormatter` must keep it a team (abbreviation, name or id).\n *\n * Give it to the team axis alone, as a prop of the composition API's `<ChartsXAxis>` / `<ChartsYAxis>`: a chart's\n * `slots` reach every axis (a value axis' label \"12\" would resolve as team id 12), and in `@mui/x-charts` 9.15 the\n * `slots` of an axis in the `xAxis` prop are dropped by `BarChart` and the other single-component charts.\n *\n * @example\n * ```tsx\n * import { BarPlot } from \"@mui/x-charts/BarChart\";\n * import { ChartsContainer } from \"@mui/x-charts/ChartsContainer\";\n * import { ChartsXAxis } from \"@mui/x-charts/ChartsXAxis\";\n * import { ChartsYAxis } from \"@mui/x-charts/ChartsYAxis\";\n * import { loadLeague } from \"@sportsdataverse/sdvplot\";\n * import { logoTickLabel } from \"@sportsdataverse/sdvplot/mui\";\n *\n * await loadLeague(\"nfl\");\n * const teams = [\"KC\", \"BUF\", \"BAL\"];\n * <ChartsContainer\n * width={400}\n * height={260}\n * xAxis={[{ id: \"teams\", scaleType: \"band\", data: teams, height: 40 }]}\n * yAxis={[{ id: \"wins\" }]}\n * series={[{ type: \"bar\", data: [15, 13, 12] }]}\n * >\n * <BarPlot />\n * <ChartsXAxis axisId=\"teams\" slots={{ axisTickLabel: logoTickLabel({ league: \"nfl\", size: 28 }) }} />\n * <ChartsYAxis axisId=\"wins\" />\n * </ChartsContainer>;\n * ```\n */\nexport function logoTickLabel(o: MuiTickLabelOptions): MuiTickLabelSlot {\n const h = checkSize(o.size ?? 24);\n const kind = o.markType ?? \"logo\";\n return (props) => {\n const x = px(props.x) ?? 0;\n const y = px(props.y) ?? 0;\n const text = props.text ?? \"\";\n const p = text === \"\" ? undefined : teamPlace(x, y, text, o);\n if (p === undefined) {\n // the label as MUI's ChartsText draws one line of it: the anchors as attributes, the rest of the style kept\n const { textAnchor, dominantBaseline, ...style } = props.style ?? {};\n return createElement(\n \"text\",\n { x, y, className: props.className, textAnchor, dominantBaseline, style },\n text,\n );\n }\n const w = h * aspect(p);\n const box = anchored(x, y, w, h, props.style);\n return image(p, kind, placedName(p, o.league, text), { ...box, h }, { x, y }, o.embed, {\n className: props.className,\n });\n };\n}\n"]}
|