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.
Files changed (49) hide show
  1. sdvplot/__init__.py +68 -0
  2. sdvplot/_cache.py +484 -0
  3. sdvplot/_colors.py +206 -0
  4. sdvplot/_contrast.py +60 -0
  5. sdvplot/_court.py +110 -0
  6. sdvplot/_deprecate.py +39 -0
  7. sdvplot/_dispatch.py +342 -0
  8. sdvplot/_errors.py +93 -0
  9. sdvplot/_headshots.py +131 -0
  10. sdvplot/_images.py +292 -0
  11. sdvplot/_index.py +178 -0
  12. sdvplot/_lazy.py +15 -0
  13. sdvplot/_manifest.py +73 -0
  14. sdvplot/_marks.py +296 -0
  15. sdvplot/_normalize.py +82 -0
  16. sdvplot/_placement.py +168 -0
  17. sdvplot/_resolve.py +363 -0
  18. sdvplot/_surface.py +220 -0
  19. sdvplot/_tables.py +87 -0
  20. sdvplot/_tiers.py +135 -0
  21. sdvplot/_types.py +38 -0
  22. sdvplot/_versions.py +30 -0
  23. sdvplot/_web.py +61 -0
  24. sdvplot/altair.py +652 -0
  25. sdvplot/bokeh.py +323 -0
  26. sdvplot/data/INDEX_VERSION +1 -0
  27. sdvplot/data/aliases.parquet +0 -0
  28. sdvplot/data/teams.parquet +0 -0
  29. sdvplot/folium.py +316 -0
  30. sdvplot/great_tables/__init__.py +267 -0
  31. sdvplot/great_tables/_cells.py +1814 -0
  32. sdvplot/great_tables/_export.py +844 -0
  33. sdvplot/great_tables/_layout.py +1802 -0
  34. sdvplot/great_tables/_marks.py +798 -0
  35. sdvplot/great_tables/_themes.py +2134 -0
  36. sdvplot/holoviews.py +303 -0
  37. sdvplot/matplotlib.py +934 -0
  38. sdvplot/plotly.py +595 -0
  39. sdvplot/plotnine.py +1104 -0
  40. sdvplot/plottable.py +141 -0
  41. sdvplot/py.typed +0 -0
  42. sdvplot/pygal.py +416 -0
  43. sdvplot/reactable.py +488 -0
  44. sdvplot/testing.py +579 -0
  45. sdvplot/typing.py +21 -0
  46. sdvplot-0.1.0.dist-info/METADATA +188 -0
  47. sdvplot-0.1.0.dist-info/RECORD +49 -0
  48. sdvplot-0.1.0.dist-info/WHEEL +4 -0
  49. 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