sdvplot 0.1.0__py3-none-any.whl
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.
- sdvplot/__init__.py +68 -0
- sdvplot/_cache.py +484 -0
- sdvplot/_colors.py +206 -0
- sdvplot/_contrast.py +60 -0
- sdvplot/_court.py +110 -0
- sdvplot/_deprecate.py +39 -0
- sdvplot/_dispatch.py +342 -0
- sdvplot/_errors.py +93 -0
- sdvplot/_headshots.py +131 -0
- sdvplot/_images.py +292 -0
- sdvplot/_index.py +178 -0
- sdvplot/_lazy.py +15 -0
- sdvplot/_manifest.py +73 -0
- sdvplot/_marks.py +296 -0
- sdvplot/_normalize.py +82 -0
- sdvplot/_placement.py +168 -0
- sdvplot/_resolve.py +363 -0
- sdvplot/_surface.py +220 -0
- sdvplot/_tables.py +87 -0
- sdvplot/_tiers.py +135 -0
- sdvplot/_types.py +38 -0
- sdvplot/_versions.py +30 -0
- sdvplot/_web.py +61 -0
- sdvplot/altair.py +652 -0
- sdvplot/bokeh.py +323 -0
- sdvplot/data/INDEX_VERSION +1 -0
- sdvplot/data/aliases.parquet +0 -0
- sdvplot/data/teams.parquet +0 -0
- sdvplot/folium.py +316 -0
- sdvplot/great_tables/__init__.py +267 -0
- sdvplot/great_tables/_cells.py +1814 -0
- sdvplot/great_tables/_export.py +844 -0
- sdvplot/great_tables/_layout.py +1802 -0
- sdvplot/great_tables/_marks.py +798 -0
- sdvplot/great_tables/_themes.py +2134 -0
- sdvplot/holoviews.py +303 -0
- sdvplot/matplotlib.py +934 -0
- sdvplot/plotly.py +595 -0
- sdvplot/plotnine.py +1104 -0
- sdvplot/plottable.py +141 -0
- sdvplot/py.typed +0 -0
- sdvplot/pygal.py +416 -0
- sdvplot/reactable.py +488 -0
- sdvplot/testing.py +579 -0
- sdvplot/typing.py +21 -0
- sdvplot-0.1.0.dist-info/METADATA +188 -0
- sdvplot-0.1.0.dist-info/RECORD +49 -0
- sdvplot-0.1.0.dist-info/WHEEL +4 -0
- sdvplot-0.1.0.dist-info/licenses/LICENSE +21 -0
sdvplot/bokeh.py
ADDED
|
@@ -0,0 +1,323 @@
|
|
|
1
|
+
"""The Bokeh adapter: logos, wordmarks and headshots as one ``image_url`` glyph per call on a Bokeh figure.
|
|
2
|
+
|
|
3
|
+
Images are sized in screen pixels, so they keep their size when the user zooms: ``height`` times the reference height,
|
|
4
|
+
which is the figure's ``frame_height`` (the plot area) when set, else its ``height`` (the whole canvas: set
|
|
5
|
+
``frame_height`` for exact sizing). Factor (categorical) and datetime axes work natively, because ``x``/``y`` hold the
|
|
6
|
+
caller's own values. HoloViews draws through ``_draw`` too.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
from typing import Any
|
|
12
|
+
|
|
13
|
+
from sdvplot._errors import UnsupportedTargetError, requires_extra
|
|
14
|
+
|
|
15
|
+
with requires_extra("bokeh"):
|
|
16
|
+
from bokeh.models import ColumnDataSource
|
|
17
|
+
|
|
18
|
+
from sdvplot._placement import Placement, check_alpha, check_height, place
|
|
19
|
+
from sdvplot._web import aspect, image_sources
|
|
20
|
+
|
|
21
|
+
_SUPPORTS_AXIS_LOGOS = False
|
|
22
|
+
|
|
23
|
+
__all__ = ["add_headshots", "add_logos", "add_wordmarks", "axis_logos"]
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def __dir__() -> list[str]: # dir() and tab completion show the public API only
|
|
27
|
+
return list(__all__)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def _figure(target: Any) -> Any:
|
|
31
|
+
if not hasattr(target, "image_url") or not hasattr(target, "frame_height"):
|
|
32
|
+
raise UnsupportedTargetError(f"sdvplot.bokeh draws on a bokeh.plotting figure, got {type(target).__name__}")
|
|
33
|
+
return target
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def _reference_height(fig: Any) -> float:
|
|
37
|
+
"""The pixel height ``height`` is a fraction of: ``frame_height`` when set, else the figure's ``height``."""
|
|
38
|
+
h = fig.frame_height or fig.height
|
|
39
|
+
if not h: # a responsive figure (e.g. a HoloViews plot with responsive=True) has neither
|
|
40
|
+
raise ValueError(
|
|
41
|
+
"sdvplot sizes images from the figure's pixel height, but it has none; set frame_height (or height), "
|
|
42
|
+
"e.g. figure(frame_height=400) or .opts(frame_height=400) in HoloViews"
|
|
43
|
+
)
|
|
44
|
+
return float(h)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def _draw(fig: Any, placements: list[Placement], sources: list[str], *, kind: str, height: float, alpha: float) -> Any:
|
|
48
|
+
"""One ``image_url`` renderer named ``sdvplot_<kind>`` with every placement, centred on its (x, y)."""
|
|
49
|
+
if not placements:
|
|
50
|
+
return None
|
|
51
|
+
h = height * _reference_height(fig)
|
|
52
|
+
data = {
|
|
53
|
+
"url": sources,
|
|
54
|
+
"x": [p.x for p in placements],
|
|
55
|
+
"y": [p.y for p in placements],
|
|
56
|
+
"w": [h * aspect(p) for p in placements],
|
|
57
|
+
"h": [h] * len(placements),
|
|
58
|
+
"team_id": [p.team_id for p in placements],
|
|
59
|
+
}
|
|
60
|
+
return fig.image_url(
|
|
61
|
+
url="url", x="x", y="y", w="w", h="h", w_units="screen", h_units="screen", anchor="center",
|
|
62
|
+
global_alpha=alpha, source=ColumnDataSource(data), name=f"sdvplot_{kind}",
|
|
63
|
+
) # fmt: skip
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def _add(
|
|
67
|
+
target: Any,
|
|
68
|
+
x: Any,
|
|
69
|
+
y: Any,
|
|
70
|
+
teams: Any,
|
|
71
|
+
*,
|
|
72
|
+
kind: str,
|
|
73
|
+
league: str,
|
|
74
|
+
season: Any,
|
|
75
|
+
height: float,
|
|
76
|
+
alpha: float,
|
|
77
|
+
variant: str,
|
|
78
|
+
embed: bool,
|
|
79
|
+
id_system: str,
|
|
80
|
+
) -> Any:
|
|
81
|
+
h, a = check_height(height), check_alpha(alpha)
|
|
82
|
+
fig = _figure(target)
|
|
83
|
+
placements = place(x, y, teams, league=league, season=season, kind=kind, variant=variant, id_system=id_system)
|
|
84
|
+
_draw(fig, placements, image_sources(placements, embed=embed), kind=kind, height=h, alpha=a)
|
|
85
|
+
return fig
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def add_logos(
|
|
89
|
+
target: Any,
|
|
90
|
+
x: Any,
|
|
91
|
+
y: Any,
|
|
92
|
+
teams: Any,
|
|
93
|
+
*,
|
|
94
|
+
league: str,
|
|
95
|
+
season: Any = None,
|
|
96
|
+
height: float = 0.1,
|
|
97
|
+
alpha: float = 1,
|
|
98
|
+
variant: str = "default",
|
|
99
|
+
embed: bool = False,
|
|
100
|
+
id_system: str = "auto",
|
|
101
|
+
) -> Any:
|
|
102
|
+
"""Draw each team's logo centred on its (x, y) point of a Bokeh figure.
|
|
103
|
+
|
|
104
|
+
The logos are sized in screen pixels, so they keep their size when the user zooms. ``height`` is a fraction of
|
|
105
|
+
the figure's ``frame_height`` (the plot area) when it is set, else of its ``height`` (the whole canvas, a little
|
|
106
|
+
taller than the plot area): set ``frame_height`` for the same sizing as the other libraries.
|
|
107
|
+
|
|
108
|
+
Args:
|
|
109
|
+
target: A ``bokeh.plotting.figure``.
|
|
110
|
+
x: The points' x positions, in the figure's x values (numbers, factors or datetimes).
|
|
111
|
+
y: The points' y positions, the same length as ``x``.
|
|
112
|
+
teams: The team for each point, in any id system ``resolve()`` understands.
|
|
113
|
+
league: The SDV league key, e.g. "nfl".
|
|
114
|
+
season: One season, or one per point, to pick each team's mark for that era.
|
|
115
|
+
height: The logo height as a fraction of the reference height, in (0, 1].
|
|
116
|
+
alpha: Opacity, 0 to 1.
|
|
117
|
+
variant: "default", "dark", or a named variant from ``marks()``.
|
|
118
|
+
embed: Inline each image as a data URI (HTML that renders offline) instead of linking its URL.
|
|
119
|
+
id_system: The id system of ``teams``; "auto" tries each in order.
|
|
120
|
+
|
|
121
|
+
Returns:
|
|
122
|
+
object: ``target`` itself, with one ``image_url`` renderer named ``sdvplot_logo`` added.
|
|
123
|
+
|
|
124
|
+
Raises:
|
|
125
|
+
InputError: (a ValueError) If ``height`` or ``alpha`` is out of range, ``league``, ``id_system`` or ``variant``
|
|
126
|
+
is unknown, or ``season`` is not a year or is outside the seasons sdvplot knows for the league.
|
|
127
|
+
ValueError: If the inputs differ in length, or the figure has no pixel height (neither ``frame_height`` nor
|
|
128
|
+
``height`` is set).
|
|
129
|
+
UnsupportedTargetError: (a TypeError) If ``target`` is not a Bokeh figure.
|
|
130
|
+
OfflineError: If the logo manifest, or with ``embed=True`` a mark's image, is neither cached nor downloadable (a
|
|
131
|
+
DownloadError, also an OSError, when the CDN answers with an error status; an IntegrityError when it sends a
|
|
132
|
+
file that does not match the manifest's sha256).
|
|
133
|
+
UnsafeDownloadError: (an OSError) If a download is refused: larger than the byte cap, past the deadline, or
|
|
134
|
+
redirected away from https.
|
|
135
|
+
UnsafeCachePathError: (a ValueError) If ``embed=True`` and the manifest's sha256 or extension for a mark would
|
|
136
|
+
put the file outside the cache directory.
|
|
137
|
+
|
|
138
|
+
Example:
|
|
139
|
+
::
|
|
140
|
+
|
|
141
|
+
from bokeh.plotting import figure
|
|
142
|
+
import sdvplot
|
|
143
|
+
|
|
144
|
+
p = figure(frame_width=400, frame_height=300)
|
|
145
|
+
p.scatter([10, 20], [-3, -7])
|
|
146
|
+
sdvplot.add_logos(p, [10, 20], [-3, -7], ["KC", "BUF"], league="nfl", height=0.15)
|
|
147
|
+
|
|
148
|
+
See Also:
|
|
149
|
+
sdvplotR geom_nfl_logos(): https://sdvplotR.sportsdataverse.org/ ;
|
|
150
|
+
Bokeh ImageURL: https://docs.bokeh.org/en/latest/docs/reference/models/glyphs/image_url.html
|
|
151
|
+
"""
|
|
152
|
+
return _add(
|
|
153
|
+
target, x, y, teams, kind="logo", league=league, season=season, height=height, alpha=alpha,
|
|
154
|
+
variant=variant, embed=embed, id_system=id_system,
|
|
155
|
+
) # fmt: skip
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
def add_wordmarks(
|
|
159
|
+
target: Any,
|
|
160
|
+
x: Any,
|
|
161
|
+
y: Any,
|
|
162
|
+
teams: Any,
|
|
163
|
+
*,
|
|
164
|
+
league: str,
|
|
165
|
+
season: Any = None,
|
|
166
|
+
height: float = 0.1,
|
|
167
|
+
alpha: float = 1,
|
|
168
|
+
variant: str = "default",
|
|
169
|
+
embed: bool = False,
|
|
170
|
+
id_system: str = "auto",
|
|
171
|
+
) -> Any:
|
|
172
|
+
"""Draw each team's wordmark centred on its (x, y) point of a Bokeh figure.
|
|
173
|
+
|
|
174
|
+
Args:
|
|
175
|
+
target: A ``bokeh.plotting.figure``.
|
|
176
|
+
x: The points' x positions, in the figure's x values.
|
|
177
|
+
y: The points' y positions, the same length as ``x``.
|
|
178
|
+
teams: The team for each point, in any id system ``resolve()`` understands.
|
|
179
|
+
league: The SDV league key, e.g. "nfl".
|
|
180
|
+
season: One season, or one per point.
|
|
181
|
+
height: The wordmark height as a fraction of the reference height (see ``add_logos``), in (0, 1].
|
|
182
|
+
alpha: Opacity, 0 to 1.
|
|
183
|
+
variant: "default", "dark", or a named variant from ``marks()``.
|
|
184
|
+
embed: Inline each image as a data URI instead of linking its URL.
|
|
185
|
+
id_system: The id system of ``teams``; "auto" tries each in order.
|
|
186
|
+
|
|
187
|
+
Returns:
|
|
188
|
+
object: ``target`` itself, with one renderer named ``sdvplot_wordmark`` added.
|
|
189
|
+
|
|
190
|
+
Raises:
|
|
191
|
+
InputError: (a ValueError) If ``height`` or ``alpha`` is out of range, ``league``, ``id_system`` or ``variant``
|
|
192
|
+
is unknown, or ``season`` is not a year or is outside the seasons sdvplot knows for the league.
|
|
193
|
+
ValueError: If the inputs differ in length, or the figure has no pixel height (neither ``frame_height`` nor
|
|
194
|
+
``height`` is set).
|
|
195
|
+
UnsupportedTargetError: (a TypeError) If ``target`` is not a Bokeh figure.
|
|
196
|
+
OfflineError: If the logo manifest, or with ``embed=True`` a mark's image, is neither cached nor downloadable (a
|
|
197
|
+
DownloadError, also an OSError, when the CDN answers with an error status; an IntegrityError when it sends a
|
|
198
|
+
file that does not match the manifest's sha256).
|
|
199
|
+
UnsafeDownloadError: (an OSError) If a download is refused: larger than the byte cap, past the deadline, or
|
|
200
|
+
redirected away from https.
|
|
201
|
+
UnsafeCachePathError: (a ValueError) If ``embed=True`` and the manifest's sha256 or extension for a mark would
|
|
202
|
+
put the file outside the cache directory.
|
|
203
|
+
|
|
204
|
+
Example:
|
|
205
|
+
::
|
|
206
|
+
|
|
207
|
+
from bokeh.plotting import figure
|
|
208
|
+
import sdvplot
|
|
209
|
+
|
|
210
|
+
p = figure(x_range=["KC", "BUF"], frame_height=300)
|
|
211
|
+
p.vbar(x=["KC", "BUF"], top=[12, 10], width=0.8)
|
|
212
|
+
sdvplot.add_wordmarks(p, ["KC", "BUF"], [12, 10], ["KC", "BUF"], league="nfl", height=0.06)
|
|
213
|
+
|
|
214
|
+
See Also:
|
|
215
|
+
sdvplotR geom_nfl_wordmarks(): https://sdvplotR.sportsdataverse.org/
|
|
216
|
+
"""
|
|
217
|
+
return _add(
|
|
218
|
+
target, x, y, teams, kind="wordmark", league=league, season=season, height=height, alpha=alpha,
|
|
219
|
+
variant=variant, embed=embed, id_system=id_system,
|
|
220
|
+
) # fmt: skip
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
def add_headshots(
|
|
224
|
+
target: Any,
|
|
225
|
+
x: Any,
|
|
226
|
+
y: Any,
|
|
227
|
+
players: Any,
|
|
228
|
+
*,
|
|
229
|
+
league: str,
|
|
230
|
+
height: float = 0.1,
|
|
231
|
+
alpha: float = 1,
|
|
232
|
+
embed: bool = False,
|
|
233
|
+
id_system: str = "espn",
|
|
234
|
+
) -> Any:
|
|
235
|
+
"""Draw each player's headshot centred on its (x, y) point of a Bokeh figure.
|
|
236
|
+
|
|
237
|
+
Args:
|
|
238
|
+
target: A ``bokeh.plotting.figure``.
|
|
239
|
+
x: The points' x positions, in the figure's x values.
|
|
240
|
+
y: The points' y positions, the same length as ``x``.
|
|
241
|
+
players: The player id for each point.
|
|
242
|
+
league: The SDV league key, e.g. "nfl".
|
|
243
|
+
height: The headshot height as a fraction of the reference height (see ``add_logos``), in (0, 1].
|
|
244
|
+
alpha: Opacity, 0 to 1.
|
|
245
|
+
embed: Inline each image as a data URI instead of linking its URL.
|
|
246
|
+
id_system: "espn" (ESPN athlete ids) or "gsis" (NFL), as in ``headshot_url``.
|
|
247
|
+
|
|
248
|
+
Returns:
|
|
249
|
+
object: ``target`` itself, with one renderer named ``sdvplot_headshot`` added.
|
|
250
|
+
|
|
251
|
+
Raises:
|
|
252
|
+
InputError: (a ValueError) If ``height`` or ``alpha`` is out of range, ``league`` has no ESPN headshots, or
|
|
253
|
+
``id_system`` is not valid for ``league``.
|
|
254
|
+
ValueError: If the inputs differ in length, or the figure has no pixel height (neither ``frame_height`` nor
|
|
255
|
+
``height`` is set).
|
|
256
|
+
UnsupportedTargetError: (a TypeError) If ``target`` is not a Bokeh figure.
|
|
257
|
+
OfflineError: If the nflverse player table (``id_system="gsis"``), or with ``embed=True`` a headshot, is neither
|
|
258
|
+
cached nor downloadable (a DownloadError, also an OSError, for an HTTP error status).
|
|
259
|
+
UnsafeDownloadError: (an OSError) If a download is refused: larger than the byte cap, past the deadline, or
|
|
260
|
+
redirected away from https.
|
|
261
|
+
|
|
262
|
+
Example:
|
|
263
|
+
::
|
|
264
|
+
|
|
265
|
+
from bokeh.plotting import figure
|
|
266
|
+
import sdvplot
|
|
267
|
+
|
|
268
|
+
p = figure(frame_height=300)
|
|
269
|
+
sdvplot.add_headshots(p, [0.5], [0.5], ["3139477"], league="nfl", height=0.2)
|
|
270
|
+
|
|
271
|
+
See Also:
|
|
272
|
+
sdvplotR geom_nfl_headshots(): https://sdvplotR.sportsdataverse.org/
|
|
273
|
+
"""
|
|
274
|
+
return _add(
|
|
275
|
+
target, x, y, players, kind="headshot", league=league, season=None, height=height, alpha=alpha,
|
|
276
|
+
variant="default", embed=embed, id_system=id_system,
|
|
277
|
+
) # fmt: skip
|
|
278
|
+
|
|
279
|
+
|
|
280
|
+
def axis_logos(target: Any, axis: str, **kwargs: Any) -> Any:
|
|
281
|
+
"""Not supported on Bokeh: Bokeh glyphs cannot sit outside the plot frame at a fixed pixel offset.
|
|
282
|
+
|
|
283
|
+
Args:
|
|
284
|
+
target: A Bokeh figure.
|
|
285
|
+
axis: "x" or "y".
|
|
286
|
+
**kwargs: Ignored.
|
|
287
|
+
|
|
288
|
+
Returns:
|
|
289
|
+
object: Never returns.
|
|
290
|
+
|
|
291
|
+
Raises:
|
|
292
|
+
UnsupportedTargetError: (a TypeError) Always. Draw the logos inside the plot with ``add_logos`` at a y below the
|
|
293
|
+
bars, or use the matplotlib, Plotly or Altair adapter for axis logos.
|
|
294
|
+
|
|
295
|
+
Example:
|
|
296
|
+
::
|
|
297
|
+
|
|
298
|
+
from bokeh.plotting import figure
|
|
299
|
+
import sdvplot
|
|
300
|
+
|
|
301
|
+
try:
|
|
302
|
+
sdvplot.axis_logos(figure(), "x", league="nfl")
|
|
303
|
+
except TypeError:
|
|
304
|
+
pass # raised: Bokeh has no axis logos yet
|
|
305
|
+
|
|
306
|
+
See Also:
|
|
307
|
+
sdvplotR element_sdv_logo(): https://sdvplotR.sportsdataverse.org/
|
|
308
|
+
"""
|
|
309
|
+
raise UnsupportedTargetError(
|
|
310
|
+
"Bokeh has no axis logos yet: draw them inside the plot with add_logos (e.g. at a y just below the bars), "
|
|
311
|
+
"or use matplotlib, Plotly or Altair for axis logos"
|
|
312
|
+
)
|
|
313
|
+
|
|
314
|
+
|
|
315
|
+
def _drawn_marks(target: Any) -> list[tuple[Any, ...]]:
|
|
316
|
+
"""Test hook: (team_id, x, y, height, url) for each image of the sdvplot renderers, height = h / reference."""
|
|
317
|
+
ref = _reference_height(target)
|
|
318
|
+
out = []
|
|
319
|
+
for r in target.renderers:
|
|
320
|
+
if (r.name or "").startswith("sdvplot_"):
|
|
321
|
+
d = r.data_source.data
|
|
322
|
+
out += list(zip(d["team_id"], d["x"], d["y"], [h / ref for h in d["h"]], d["url"], strict=True))
|
|
323
|
+
return out
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
88b114b58b3d
|
|
Binary file
|
|
Binary file
|
sdvplot/folium.py
ADDED
|
@@ -0,0 +1,316 @@
|
|
|
1
|
+
"""The Folium adapter: logos, wordmarks and headshots as map markers with image icons.
|
|
2
|
+
|
|
3
|
+
On a map, ``x`` is longitude and ``y`` latitude. A map has no plot height, so ``height`` is a fraction of the map's
|
|
4
|
+
pixel height when it is given in pixels, else of _FOLIUM_REFERENCE_HEIGHT (a notebook map's usual height). The markers
|
|
5
|
+
go into one ``FeatureGroup`` named "sdvplot logos", so a ``LayerControl`` can toggle them.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
from typing import Any
|
|
11
|
+
|
|
12
|
+
from sdvplot._errors import UnsupportedTargetError, requires_extra
|
|
13
|
+
|
|
14
|
+
with requires_extra("folium"):
|
|
15
|
+
import folium
|
|
16
|
+
|
|
17
|
+
from sdvplot._index import teams as team_index
|
|
18
|
+
from sdvplot._placement import check_alpha, check_height, place
|
|
19
|
+
from sdvplot._web import aspect, image_sources
|
|
20
|
+
|
|
21
|
+
_SUPPORTS_AXIS_LOGOS = False
|
|
22
|
+
_FOLIUM_REFERENCE_HEIGHT = 500 # px: the reference for a map whose height is not in pixels (the default "100%")
|
|
23
|
+
_GROUP_NAME = "sdvplot logos"
|
|
24
|
+
|
|
25
|
+
__all__ = ["add_headshots", "add_logos", "add_wordmarks", "axis_logos"]
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def __dir__() -> list[str]: # dir() and tab completion show the public API only
|
|
29
|
+
return list(__all__)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def _map(target: Any) -> folium.Map:
|
|
33
|
+
if not isinstance(target, folium.Map):
|
|
34
|
+
raise UnsupportedTargetError(f"sdvplot.folium draws on a folium.Map, got {type(target).__name__}")
|
|
35
|
+
return target
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def _reference_height(m: folium.Map) -> float:
|
|
39
|
+
"""The pixel height ``height`` is a fraction of: the map's own when in pixels, else _FOLIUM_REFERENCE_HEIGHT."""
|
|
40
|
+
value, unit = m.height
|
|
41
|
+
return float(value) if unit == "px" else float(_FOLIUM_REFERENCE_HEIGHT)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def _group(m: folium.Map) -> folium.FeatureGroup:
|
|
45
|
+
for child in m._children.values():
|
|
46
|
+
if isinstance(child, folium.FeatureGroup) and child.layer_name == _GROUP_NAME:
|
|
47
|
+
return child
|
|
48
|
+
group = folium.FeatureGroup(name=_GROUP_NAME)
|
|
49
|
+
group.add_to(m)
|
|
50
|
+
return group
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def _add(
|
|
54
|
+
target: Any,
|
|
55
|
+
x: Any,
|
|
56
|
+
y: Any,
|
|
57
|
+
teams: Any,
|
|
58
|
+
*,
|
|
59
|
+
kind: str,
|
|
60
|
+
league: str,
|
|
61
|
+
season: Any,
|
|
62
|
+
height: float,
|
|
63
|
+
alpha: float,
|
|
64
|
+
variant: str,
|
|
65
|
+
embed: bool,
|
|
66
|
+
id_system: str,
|
|
67
|
+
) -> Any:
|
|
68
|
+
h, a = check_height(height), check_alpha(alpha)
|
|
69
|
+
m = _map(target)
|
|
70
|
+
placements = place(x, y, teams, league=league, season=season, kind=kind, variant=variant, id_system=id_system)
|
|
71
|
+
if not placements:
|
|
72
|
+
return m
|
|
73
|
+
names = {} if kind == "headshot" else dict(team_index(league).select("team_id", "name").iter_rows())
|
|
74
|
+
px = max(1, round(h * _reference_height(m))) # Leaflet icon sizes are whole pixels
|
|
75
|
+
group = _group(m)
|
|
76
|
+
for p, src in zip(placements, image_sources(placements, embed=embed), strict=True):
|
|
77
|
+
w = max(1, round(px * aspect(p)))
|
|
78
|
+
marker: Any = folium.Marker( # Any: it carries the test hook below (a type: ignore is unused without folium)
|
|
79
|
+
location=[p.y, p.x],
|
|
80
|
+
icon=folium.CustomIcon(src, icon_size=(w, px), icon_anchor=(w // 2, px // 2)),
|
|
81
|
+
tooltip=names.get(p.team_id, p.team_id),
|
|
82
|
+
opacity=a,
|
|
83
|
+
)
|
|
84
|
+
marker._sdvplot_mark = (p.team_id, p.x, p.y, src)
|
|
85
|
+
marker.add_to(group)
|
|
86
|
+
return m
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def add_logos(
|
|
90
|
+
target: Any,
|
|
91
|
+
x: Any,
|
|
92
|
+
y: Any,
|
|
93
|
+
teams: Any,
|
|
94
|
+
*,
|
|
95
|
+
league: str,
|
|
96
|
+
season: Any = None,
|
|
97
|
+
height: float = 0.1,
|
|
98
|
+
alpha: float = 1,
|
|
99
|
+
variant: str = "default",
|
|
100
|
+
embed: bool = False,
|
|
101
|
+
id_system: str = "auto",
|
|
102
|
+
) -> Any:
|
|
103
|
+
"""Put each team's logo on a Folium map at its (longitude, latitude), with the team name as a tooltip.
|
|
104
|
+
|
|
105
|
+
Args:
|
|
106
|
+
target: A ``folium.Map``.
|
|
107
|
+
x: The longitudes.
|
|
108
|
+
y: The latitudes, the same length as ``x``.
|
|
109
|
+
teams: The team for each point, in any id system ``resolve()`` understands.
|
|
110
|
+
league: The SDV league key, e.g. "nfl".
|
|
111
|
+
season: One season, or one per point, to pick each team's mark for that era.
|
|
112
|
+
height: The logo height as a fraction of the map's pixel height (or of _FOLIUM_REFERENCE_HEIGHT, 500 px, when
|
|
113
|
+
the map height is not in pixels), in (0, 1].
|
|
114
|
+
alpha: Opacity, 0 to 1.
|
|
115
|
+
variant: "default", "dark", or a named variant from ``marks()``.
|
|
116
|
+
embed: Inline each image as a data URI (a map that renders offline) instead of linking its URL.
|
|
117
|
+
id_system: The id system of ``teams``; "auto" tries each in order.
|
|
118
|
+
|
|
119
|
+
Returns:
|
|
120
|
+
object: ``target`` itself, with the markers in the "sdvplot logos" feature group.
|
|
121
|
+
|
|
122
|
+
Raises:
|
|
123
|
+
InputError: (a ValueError) If ``height`` or ``alpha`` is out of range, ``league``, ``id_system`` or ``variant``
|
|
124
|
+
is unknown, or ``season`` is not a year or is outside the seasons sdvplot knows for the league.
|
|
125
|
+
ValueError: If the inputs differ in length, or a location is not a number.
|
|
126
|
+
UnsupportedTargetError: (a TypeError) If ``target`` is not a ``folium.Map``.
|
|
127
|
+
OfflineError: If the logo manifest, or with ``embed=True`` a mark's image, is neither cached nor downloadable (a
|
|
128
|
+
DownloadError, also an OSError, when the CDN answers with an error status; an IntegrityError when it sends a
|
|
129
|
+
file that does not match the manifest's sha256).
|
|
130
|
+
UnsafeDownloadError: (an OSError) If a download is refused: larger than the byte cap, past the deadline, or
|
|
131
|
+
redirected away from https.
|
|
132
|
+
UnsafeCachePathError: (a ValueError) If ``embed=True`` and the manifest's sha256 or extension for a mark would
|
|
133
|
+
put the file outside the cache directory.
|
|
134
|
+
|
|
135
|
+
Example:
|
|
136
|
+
::
|
|
137
|
+
|
|
138
|
+
import folium
|
|
139
|
+
import sdvplot
|
|
140
|
+
|
|
141
|
+
m = folium.Map(location=[39, -95], zoom_start=4, height=600)
|
|
142
|
+
sdvplot.add_logos(m, [-94.48, -78.79], [39.05, 42.77], ["KC", "BUF"], league="nfl", height=0.06)
|
|
143
|
+
|
|
144
|
+
See Also:
|
|
145
|
+
sdvplotR: https://sdvplotR.sportsdataverse.org/ ;
|
|
146
|
+
Folium CustomIcon: https://python-visualization.github.io/folium/latest/user_guide/ui_elements/icons.html
|
|
147
|
+
"""
|
|
148
|
+
return _add(
|
|
149
|
+
target, x, y, teams, kind="logo", league=league, season=season, height=height, alpha=alpha,
|
|
150
|
+
variant=variant, embed=embed, id_system=id_system,
|
|
151
|
+
) # fmt: skip
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
def add_wordmarks(
|
|
155
|
+
target: Any,
|
|
156
|
+
x: Any,
|
|
157
|
+
y: Any,
|
|
158
|
+
teams: Any,
|
|
159
|
+
*,
|
|
160
|
+
league: str,
|
|
161
|
+
season: Any = None,
|
|
162
|
+
height: float = 0.1,
|
|
163
|
+
alpha: float = 1,
|
|
164
|
+
variant: str = "default",
|
|
165
|
+
embed: bool = False,
|
|
166
|
+
id_system: str = "auto",
|
|
167
|
+
) -> Any:
|
|
168
|
+
"""Put each team's wordmark on a Folium map at its (longitude, latitude).
|
|
169
|
+
|
|
170
|
+
Args:
|
|
171
|
+
target: A ``folium.Map``.
|
|
172
|
+
x: The longitudes.
|
|
173
|
+
y: The latitudes, the same length as ``x``.
|
|
174
|
+
teams: The team for each point, in any id system ``resolve()`` understands.
|
|
175
|
+
league: The SDV league key, e.g. "nfl".
|
|
176
|
+
season: One season, or one per point.
|
|
177
|
+
height: The wordmark height as a fraction of the map's reference height (see ``add_logos``), in (0, 1].
|
|
178
|
+
alpha: Opacity, 0 to 1.
|
|
179
|
+
variant: "default", "dark", or a named variant from ``marks()``.
|
|
180
|
+
embed: Inline each image as a data URI instead of linking its URL.
|
|
181
|
+
id_system: The id system of ``teams``; "auto" tries each in order.
|
|
182
|
+
|
|
183
|
+
Returns:
|
|
184
|
+
object: ``target`` itself, with the markers added.
|
|
185
|
+
|
|
186
|
+
Raises:
|
|
187
|
+
InputError: (a ValueError) If ``height`` or ``alpha`` is out of range, ``league``, ``id_system`` or ``variant``
|
|
188
|
+
is unknown, or ``season`` is not a year or is outside the seasons sdvplot knows for the league.
|
|
189
|
+
ValueError: If the inputs differ in length, or a location is not a number.
|
|
190
|
+
UnsupportedTargetError: (a TypeError) If ``target`` is not a ``folium.Map``.
|
|
191
|
+
OfflineError: If the logo manifest, or with ``embed=True`` a mark's image, is neither cached nor downloadable (a
|
|
192
|
+
DownloadError, also an OSError, when the CDN answers with an error status; an IntegrityError when it sends a
|
|
193
|
+
file that does not match the manifest's sha256).
|
|
194
|
+
UnsafeDownloadError: (an OSError) If a download is refused: larger than the byte cap, past the deadline, or
|
|
195
|
+
redirected away from https.
|
|
196
|
+
UnsafeCachePathError: (a ValueError) If ``embed=True`` and the manifest's sha256 or extension for a mark would
|
|
197
|
+
put the file outside the cache directory.
|
|
198
|
+
|
|
199
|
+
Example:
|
|
200
|
+
::
|
|
201
|
+
|
|
202
|
+
import folium
|
|
203
|
+
import sdvplot
|
|
204
|
+
|
|
205
|
+
m = folium.Map(location=[39, -95], zoom_start=4)
|
|
206
|
+
sdvplot.add_wordmarks(m, [-94.48], [39.05], ["KC"], league="nfl", height=0.04)
|
|
207
|
+
|
|
208
|
+
See Also:
|
|
209
|
+
sdvplotR: https://sdvplotR.sportsdataverse.org/
|
|
210
|
+
"""
|
|
211
|
+
return _add(
|
|
212
|
+
target, x, y, teams, kind="wordmark", league=league, season=season, height=height, alpha=alpha,
|
|
213
|
+
variant=variant, embed=embed, id_system=id_system,
|
|
214
|
+
) # fmt: skip
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
def add_headshots(
|
|
218
|
+
target: Any,
|
|
219
|
+
x: Any,
|
|
220
|
+
y: Any,
|
|
221
|
+
players: Any,
|
|
222
|
+
*,
|
|
223
|
+
league: str,
|
|
224
|
+
height: float = 0.1,
|
|
225
|
+
alpha: float = 1,
|
|
226
|
+
embed: bool = False,
|
|
227
|
+
id_system: str = "espn",
|
|
228
|
+
) -> Any:
|
|
229
|
+
"""Put each player's headshot on a Folium map at its (longitude, latitude).
|
|
230
|
+
|
|
231
|
+
Args:
|
|
232
|
+
target: A ``folium.Map``.
|
|
233
|
+
x: The longitudes.
|
|
234
|
+
y: The latitudes, the same length as ``x``.
|
|
235
|
+
players: The player id for each point (shown as the tooltip).
|
|
236
|
+
league: The SDV league key, e.g. "nfl".
|
|
237
|
+
height: The headshot height as a fraction of the map's reference height (see ``add_logos``), in (0, 1].
|
|
238
|
+
alpha: Opacity, 0 to 1.
|
|
239
|
+
embed: Inline each image as a data URI instead of linking its URL.
|
|
240
|
+
id_system: "espn" (ESPN athlete ids) or "gsis" (NFL), as in ``headshot_url``.
|
|
241
|
+
|
|
242
|
+
Returns:
|
|
243
|
+
object: ``target`` itself, with the markers added.
|
|
244
|
+
|
|
245
|
+
Raises:
|
|
246
|
+
InputError: (a ValueError) If ``height`` or ``alpha`` is out of range, ``league`` has no ESPN headshots, or
|
|
247
|
+
``id_system`` is not valid for ``league``.
|
|
248
|
+
ValueError: If the inputs differ in length, or a location is not a number.
|
|
249
|
+
UnsupportedTargetError: (a TypeError) If ``target`` is not a ``folium.Map``.
|
|
250
|
+
OfflineError: If the nflverse player table (``id_system="gsis"``), or with ``embed=True`` a headshot, is neither
|
|
251
|
+
cached nor downloadable (a DownloadError, also an OSError, for an HTTP error status).
|
|
252
|
+
UnsafeDownloadError: (an OSError) If a download is refused: larger than the byte cap, past the deadline, or
|
|
253
|
+
redirected away from https.
|
|
254
|
+
|
|
255
|
+
Example:
|
|
256
|
+
::
|
|
257
|
+
|
|
258
|
+
import folium
|
|
259
|
+
import sdvplot
|
|
260
|
+
|
|
261
|
+
m = folium.Map(location=[39, -95], zoom_start=4)
|
|
262
|
+
sdvplot.add_headshots(m, [-94.48], [39.05], ["3139477"], league="nfl", height=0.08)
|
|
263
|
+
|
|
264
|
+
See Also:
|
|
265
|
+
sdvplotR: https://sdvplotR.sportsdataverse.org/
|
|
266
|
+
"""
|
|
267
|
+
return _add(
|
|
268
|
+
target, x, y, players, kind="headshot", league=league, season=None, height=height, alpha=alpha,
|
|
269
|
+
variant="default", embed=embed, id_system=id_system,
|
|
270
|
+
) # fmt: skip
|
|
271
|
+
|
|
272
|
+
|
|
273
|
+
def axis_logos(target: Any, axis: str, **kwargs: Any) -> Any:
|
|
274
|
+
"""Not supported: a map has no category axes.
|
|
275
|
+
|
|
276
|
+
Args:
|
|
277
|
+
target: A ``folium.Map``.
|
|
278
|
+
axis: "x" or "y".
|
|
279
|
+
**kwargs: Ignored.
|
|
280
|
+
|
|
281
|
+
Returns:
|
|
282
|
+
object: Never returns.
|
|
283
|
+
|
|
284
|
+
Raises:
|
|
285
|
+
UnsupportedTargetError: (a TypeError) Always. Put the logos on the map with ``add_logos`` instead.
|
|
286
|
+
|
|
287
|
+
Example:
|
|
288
|
+
::
|
|
289
|
+
|
|
290
|
+
import folium
|
|
291
|
+
import sdvplot
|
|
292
|
+
|
|
293
|
+
try:
|
|
294
|
+
sdvplot.axis_logos(folium.Map(), "x", league="nfl")
|
|
295
|
+
except TypeError:
|
|
296
|
+
pass # raised: maps have no axis logos
|
|
297
|
+
|
|
298
|
+
See Also:
|
|
299
|
+
sdvplotR: https://sdvplotR.sportsdataverse.org/
|
|
300
|
+
"""
|
|
301
|
+
raise UnsupportedTargetError("a folium map has no category axes; put the logos on the map with add_logos instead")
|
|
302
|
+
|
|
303
|
+
|
|
304
|
+
def _drawn_marks(target: Any) -> list[tuple[Any, ...]]:
|
|
305
|
+
"""Test hook: (team_id, x, y, height, url) for each marker sdvplot added; height = icon px / reference px."""
|
|
306
|
+
m = _map(target)
|
|
307
|
+
ref = _reference_height(m)
|
|
308
|
+
out = []
|
|
309
|
+
for group in m._children.values():
|
|
310
|
+
if isinstance(group, folium.FeatureGroup) and group.layer_name == _GROUP_NAME:
|
|
311
|
+
markers: list[Any] = list(group._children.values())
|
|
312
|
+
for marker in markers:
|
|
313
|
+
if hasattr(marker, "_sdvplot_mark"):
|
|
314
|
+
team_id, x, y, src = marker._sdvplot_mark
|
|
315
|
+
out.append((team_id, x, y, marker.icon.options["icon_size"][1] / ref, src))
|
|
316
|
+
return out
|