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
|
@@ -0,0 +1,798 @@
|
|
|
1
|
+
"""Team marks and team identity in great_tables: ports of sdvplotR's R/gt_sdv.R and R/gt_theme_sdv.R."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import copy
|
|
6
|
+
import html
|
|
7
|
+
from collections.abc import Callable
|
|
8
|
+
from typing import Any
|
|
9
|
+
|
|
10
|
+
import narwhals as nw
|
|
11
|
+
from great_tables import GT, google_font, loc, px, random_id, style
|
|
12
|
+
from great_tables import html as gt_html
|
|
13
|
+
from great_tables._tbl_data import get_column_names
|
|
14
|
+
|
|
15
|
+
from sdvplot._colors import team_colors
|
|
16
|
+
from sdvplot._contrast import contrast, mix, on_color, solid
|
|
17
|
+
from sdvplot._errors import InputError, warn
|
|
18
|
+
from sdvplot._placement import KINDS, _missing
|
|
19
|
+
from sdvplot._resolve import one_team, resolve
|
|
20
|
+
from sdvplot._tables import check_px, mark_html
|
|
21
|
+
from sdvplot._types import HeadshotIdSystem, IdSystem
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def _check_gt(gt: Any, arg: str = "gt") -> GT:
|
|
25
|
+
"""sdvplotR's ``.check_gt``: every public table function takes a GT, never raw data; returns ``gt``."""
|
|
26
|
+
if isinstance(gt, GT):
|
|
27
|
+
return gt
|
|
28
|
+
if hasattr(gt, "columns"):
|
|
29
|
+
hint = "It looks like raw data: wrap it in great_tables.GT() first."
|
|
30
|
+
else:
|
|
31
|
+
hint = "Build a table with great_tables.GT() and pass that in."
|
|
32
|
+
raise TypeError(f"{arg} must be a great_tables GT, not {type(gt).__name__}. {hint}")
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def _check_columns(gt: GT, columns: Any) -> None:
|
|
36
|
+
"""Column names the table lacks are a ValueError on every backend (great_tables raised polars' own error and
|
|
37
|
+
matched nothing on pandas). Selectors and other expressions pass through to great_tables."""
|
|
38
|
+
names = [columns] if isinstance(columns, str) else columns
|
|
39
|
+
if isinstance(names, (list, tuple)) and all(isinstance(n, str) for n in names):
|
|
40
|
+
have = [str(c) for c in get_column_names(gt._tbl_data)] # pandas, polars or pyarrow
|
|
41
|
+
if missing := [n for n in names if n not in have]:
|
|
42
|
+
raise ValueError(f"column(s) {missing} not in the table; its columns are {have}")
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def _cell_texts(gt: GT, locations: Any) -> list[str]:
|
|
46
|
+
"""The distinct texts great_tables hands a text_transform at ``locations``, read by rendering once."""
|
|
47
|
+
seen: list[str] = []
|
|
48
|
+
|
|
49
|
+
def record(text: str) -> str:
|
|
50
|
+
seen.append(text)
|
|
51
|
+
return text
|
|
52
|
+
|
|
53
|
+
gt.text_transform(locations, record).as_raw_html()
|
|
54
|
+
return list(dict.fromkeys(seen))
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def _image_cells(
|
|
58
|
+
gt: GT,
|
|
59
|
+
columns: Any,
|
|
60
|
+
locations: Any,
|
|
61
|
+
*,
|
|
62
|
+
kind: str,
|
|
63
|
+
league: str,
|
|
64
|
+
height: Any,
|
|
65
|
+
season: Any = None,
|
|
66
|
+
include_name: bool = False,
|
|
67
|
+
id_system: str = "auto",
|
|
68
|
+
strict: bool = False,
|
|
69
|
+
) -> GT:
|
|
70
|
+
"""Cells at ``locations`` (default: the body of ``columns``) whose text resolves become their mark's <img>.
|
|
71
|
+
|
|
72
|
+
Resolution happens here, at call time, so unknown values warn once now rather than at every render (spec T4).
|
|
73
|
+
great_tables passes cells as escaped HTML text ("A&M"), so values are unescaped before resolving.
|
|
74
|
+
"""
|
|
75
|
+
_check_gt(gt)
|
|
76
|
+
h = check_px(height)
|
|
77
|
+
if locations is None:
|
|
78
|
+
_check_columns(gt, columns)
|
|
79
|
+
locs = loc.body(columns) if locations is None else locations
|
|
80
|
+
for where in locs if isinstance(locs, list) else [locs]:
|
|
81
|
+
if isinstance(where, loc.body): # loc.body(columns=...) names columns too
|
|
82
|
+
_check_columns(gt, where.columns)
|
|
83
|
+
# the locations great_tables' text_transform reaches; it escapes column labels and ignores every other one
|
|
84
|
+
if not isinstance(where, (loc.body, loc.stub, loc.row_groups)):
|
|
85
|
+
hint = (
|
|
86
|
+
"; for marks in the column labels use gt_sdv_cols_label()"
|
|
87
|
+
if isinstance(where, loc.column_labels)
|
|
88
|
+
else ""
|
|
89
|
+
)
|
|
90
|
+
raise ValueError(
|
|
91
|
+
"locations must be loc.body(), loc.stub() or loc.row_groups(), or a list of them, "
|
|
92
|
+
f"not {type(where).__name__}{hint}"
|
|
93
|
+
)
|
|
94
|
+
texts = _cell_texts(gt, locs)
|
|
95
|
+
imgs = mark_html([html.unescape(t) for t in texts], league=league, kind=kind, height=h, season=season,
|
|
96
|
+
include_name=include_name, id_system=id_system, strict=strict) # fmt: skip
|
|
97
|
+
lookup = {t: img + (t if include_name else "") for t, img in zip(texts, imgs, strict=True) if img is not None}
|
|
98
|
+
return gt.text_transform(locs, lambda text: lookup.get(text, text))
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def gt_sdv_logos(
|
|
102
|
+
gt: GT,
|
|
103
|
+
columns: Any,
|
|
104
|
+
*,
|
|
105
|
+
league: str,
|
|
106
|
+
height: Any = 30,
|
|
107
|
+
locations: Any = None,
|
|
108
|
+
include_name: bool = False,
|
|
109
|
+
season: Any = None,
|
|
110
|
+
id_system: IdSystem = "auto",
|
|
111
|
+
strict: bool = False,
|
|
112
|
+
) -> GT:
|
|
113
|
+
"""Show each cell's team as its logo in a great_tables table.
|
|
114
|
+
|
|
115
|
+
Values are resolved when you call this (team abbreviations, names and provider ids, as in ``resolve()``), so
|
|
116
|
+
unknown values warn once, now. They keep their text. The cell text is read as it renders now, so apply any
|
|
117
|
+
``fmt_*`` to the same column before this.
|
|
118
|
+
|
|
119
|
+
Args:
|
|
120
|
+
gt: A great_tables ``GT``.
|
|
121
|
+
columns: The columns whose body cells become logos: a name, a list of names, or a polars selector. Ignored when
|
|
122
|
+
``locations`` is given (pass None).
|
|
123
|
+
league: The SDV league key, e.g. "nfl".
|
|
124
|
+
height: The image height in pixels.
|
|
125
|
+
locations: Instead of the body of ``columns``: ``loc.body()``, ``loc.stub()`` or ``loc.row_groups()``, or a
|
|
126
|
+
list of them (the locations great_tables' ``text_transform`` reaches). For marks in the column labels,
|
|
127
|
+
use ``gt_sdv_cols_label``.
|
|
128
|
+
include_name: Keep the cell's text after the logo.
|
|
129
|
+
season: One season whose marks every cell shows (the ending year for the NHL, NBA, MBB and WBB); None for
|
|
130
|
+
today's.
|
|
131
|
+
id_system: The id system of the cell values, as in ``resolve``: "auto" tries each in order; NHL stats ids
|
|
132
|
+
need "nhl_id".
|
|
133
|
+
strict: Raise UnresolvedTeamError instead of warning when a value does not resolve.
|
|
134
|
+
|
|
135
|
+
Returns:
|
|
136
|
+
GT: A new table; ``gt`` is unchanged.
|
|
137
|
+
|
|
138
|
+
Raises:
|
|
139
|
+
TypeError: If ``gt`` is not a great_tables GT.
|
|
140
|
+
InputError: (a ValueError) If ``height`` is not a number of pixels of at least 1, ``league`` or ``id_system`` is
|
|
141
|
+
unknown, or ``season`` is not one year or is outside the seasons sdvplot knows for the league.
|
|
142
|
+
ValueError: If ``columns`` names a column the table lacks, or ``locations`` holds another location.
|
|
143
|
+
UnresolvedTeamError: (a ValueError) If ``strict=True`` and a value does not resolve.
|
|
144
|
+
OfflineError: If the logo manifest cannot be downloaded and no cached copy exists (a DownloadError, also an
|
|
145
|
+
OSError, when the CDN answers with an error status).
|
|
146
|
+
UnsafeDownloadError: (an OSError) If the manifest download is refused: larger than the byte cap, past the
|
|
147
|
+
deadline, or redirected away from https.
|
|
148
|
+
|
|
149
|
+
Example:
|
|
150
|
+
::
|
|
151
|
+
|
|
152
|
+
from great_tables import GT
|
|
153
|
+
from sdvplot.great_tables import gt_sdv_logos
|
|
154
|
+
import polars as pl
|
|
155
|
+
|
|
156
|
+
df = pl.DataFrame(
|
|
157
|
+
{
|
|
158
|
+
"team": ["KC", "BUF", "BAL"],
|
|
159
|
+
"espn_id": ["3139477", "3918298", "3916387"],
|
|
160
|
+
"wins": [12, 10, 9],
|
|
161
|
+
}
|
|
162
|
+
)
|
|
163
|
+
|
|
164
|
+
gt_sdv_logos(GT(df), "team", league="nfl", height=24)
|
|
165
|
+
|
|
166
|
+
See Also:
|
|
167
|
+
Ported from sdvplotR ``gt_sdv_logos()``: https://sdvplotR.sportsdataverse.org/reference/gt_sdv_logos.html ;
|
|
168
|
+
great_tables: https://posit-dev.github.io/great-tables/
|
|
169
|
+
"""
|
|
170
|
+
return _image_cells(gt, columns, locations, kind="logo", league=league, height=height, season=season,
|
|
171
|
+
include_name=include_name, id_system=id_system, strict=strict) # fmt: skip
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
def gt_sdv_wordmarks(
|
|
175
|
+
gt: GT,
|
|
176
|
+
columns: Any,
|
|
177
|
+
*,
|
|
178
|
+
league: str,
|
|
179
|
+
height: Any = 30,
|
|
180
|
+
locations: Any = None,
|
|
181
|
+
season: Any = None,
|
|
182
|
+
id_system: IdSystem = "auto",
|
|
183
|
+
strict: bool = False,
|
|
184
|
+
) -> GT:
|
|
185
|
+
"""Show each cell's team as its wordmark in a great_tables table.
|
|
186
|
+
|
|
187
|
+
Args:
|
|
188
|
+
gt: A great_tables ``GT``.
|
|
189
|
+
columns: The columns whose body cells become wordmarks. Ignored when ``locations`` is given (pass None).
|
|
190
|
+
league: The SDV league key, e.g. "nfl".
|
|
191
|
+
height: The image height in pixels.
|
|
192
|
+
locations: Instead of the body of ``columns``: ``loc.body()``, ``loc.stub()`` or ``loc.row_groups()``, or a
|
|
193
|
+
list of them (the locations great_tables' ``text_transform`` reaches). For marks in the column labels,
|
|
194
|
+
use ``gt_sdv_cols_label``.
|
|
195
|
+
season: One season whose marks every cell shows; None for today's.
|
|
196
|
+
id_system: The id system of the cell values, as in ``resolve``: "auto" tries each in order; NHL stats ids
|
|
197
|
+
need "nhl_id".
|
|
198
|
+
strict: Raise UnresolvedTeamError instead of warning when a value does not resolve.
|
|
199
|
+
|
|
200
|
+
Returns:
|
|
201
|
+
GT: A new table; unknown values keep their text, with one SdvplotWarning now.
|
|
202
|
+
|
|
203
|
+
Raises:
|
|
204
|
+
TypeError: If ``gt`` is not a great_tables GT.
|
|
205
|
+
InputError: (a ValueError) If ``height`` is not a number of pixels of at least 1, ``league`` or ``id_system`` is
|
|
206
|
+
unknown, or ``season`` is not one year or is outside the seasons sdvplot knows for the league.
|
|
207
|
+
ValueError: If ``columns`` names a column the table lacks, or ``locations`` holds another location.
|
|
208
|
+
UnresolvedTeamError: (a ValueError) If ``strict=True`` and a value does not resolve.
|
|
209
|
+
OfflineError: If the logo manifest cannot be downloaded and no cached copy exists (a DownloadError, also an
|
|
210
|
+
OSError, when the CDN answers with an error status).
|
|
211
|
+
UnsafeDownloadError: (an OSError) If the manifest download is refused: larger than the byte cap, past the
|
|
212
|
+
deadline, or redirected away from https.
|
|
213
|
+
|
|
214
|
+
Example:
|
|
215
|
+
::
|
|
216
|
+
|
|
217
|
+
from great_tables import GT
|
|
218
|
+
from sdvplot.great_tables import gt_sdv_wordmarks
|
|
219
|
+
import polars as pl
|
|
220
|
+
|
|
221
|
+
df = pl.DataFrame(
|
|
222
|
+
{
|
|
223
|
+
"team": ["KC", "BUF", "BAL"],
|
|
224
|
+
"espn_id": ["3139477", "3918298", "3916387"],
|
|
225
|
+
"wins": [12, 10, 9],
|
|
226
|
+
}
|
|
227
|
+
)
|
|
228
|
+
|
|
229
|
+
gt_sdv_wordmarks(GT(df), "team", league="nfl")
|
|
230
|
+
|
|
231
|
+
See Also:
|
|
232
|
+
Ported from sdvplotR ``gt_sdv_wordmarks()``: https://sdvplotR.sportsdataverse.org/reference/gt_sdv_wordmarks.html
|
|
233
|
+
"""
|
|
234
|
+
return _image_cells(gt, columns, locations, kind="wordmark", league=league, height=height, season=season,
|
|
235
|
+
id_system=id_system, strict=strict) # fmt: skip
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
def gt_sdv_headshots(
|
|
239
|
+
gt: GT, columns: Any, *, league: str, height: Any = 30, locations: Any = None, id_system: str = "espn"
|
|
240
|
+
) -> GT:
|
|
241
|
+
"""Show each cell's player id as the player's headshot in a great_tables table.
|
|
242
|
+
|
|
243
|
+
Args:
|
|
244
|
+
gt: A great_tables ``GT``.
|
|
245
|
+
columns: The columns of player ids. Ignored when ``locations`` is given (pass None).
|
|
246
|
+
league: The SDV league key, e.g. "nfl".
|
|
247
|
+
height: The image height in pixels.
|
|
248
|
+
locations: Instead of the body of ``columns``: ``loc.body()``, ``loc.stub()`` or ``loc.row_groups()``, or a
|
|
249
|
+
list of them (the locations great_tables' ``text_transform`` reaches). For marks in the column labels,
|
|
250
|
+
use ``gt_sdv_cols_label``.
|
|
251
|
+
id_system: "espn" (ESPN athlete ids, any ESPN league) or "gsis" (NFL), as in ``headshot_url``.
|
|
252
|
+
|
|
253
|
+
Returns:
|
|
254
|
+
GT: A new table; ids without a headshot keep their text, with one SdvplotWarning now.
|
|
255
|
+
|
|
256
|
+
Raises:
|
|
257
|
+
TypeError: If ``gt`` is not a great_tables GT.
|
|
258
|
+
InputError: (a ValueError) If ``height`` is not a number of pixels of at least 1, ``league`` has no ESPN
|
|
259
|
+
headshots, or ``id_system`` is not valid for ``league``.
|
|
260
|
+
ValueError: If ``columns`` names a column the table lacks, or ``locations`` holds another location.
|
|
261
|
+
OfflineError: If ``id_system`` is "gsis" and the nflverse player table cannot be downloaded and no cached copy
|
|
262
|
+
exists (a DownloadError, also an OSError, when GitHub answers with an error status).
|
|
263
|
+
UnsafeDownloadError: (an OSError) If ``id_system`` is "gsis" and the player table download is refused: larger
|
|
264
|
+
than the byte cap, past the deadline, or redirected away from https.
|
|
265
|
+
|
|
266
|
+
Example:
|
|
267
|
+
::
|
|
268
|
+
|
|
269
|
+
from great_tables import GT
|
|
270
|
+
from sdvplot.great_tables import gt_sdv_headshots
|
|
271
|
+
import polars as pl
|
|
272
|
+
|
|
273
|
+
df = pl.DataFrame(
|
|
274
|
+
{
|
|
275
|
+
"team": ["KC", "BUF", "BAL"],
|
|
276
|
+
"espn_id": ["3139477", "3918298", "3916387"],
|
|
277
|
+
"wins": [12, 10, 9],
|
|
278
|
+
}
|
|
279
|
+
)
|
|
280
|
+
|
|
281
|
+
gt_sdv_headshots(GT(df), "espn_id", league="nfl", height=40)
|
|
282
|
+
|
|
283
|
+
See Also:
|
|
284
|
+
Ported from sdvplotR ``gt_sdv_headshots()``: https://sdvplotR.sportsdataverse.org/reference/gt_sdv_headshots.html
|
|
285
|
+
"""
|
|
286
|
+
return _image_cells(gt, columns, locations, kind="headshot", league=league, height=height, id_system=id_system)
|
|
287
|
+
|
|
288
|
+
|
|
289
|
+
def gt_sdv_cols_label(
|
|
290
|
+
gt: GT,
|
|
291
|
+
columns: Any = None,
|
|
292
|
+
*,
|
|
293
|
+
league: str,
|
|
294
|
+
height: Any = 30,
|
|
295
|
+
season: Any = None,
|
|
296
|
+
mark_type: str = "logo",
|
|
297
|
+
id_system: IdSystem | HeadshotIdSystem | None = None,
|
|
298
|
+
strict: bool = False,
|
|
299
|
+
) -> GT:
|
|
300
|
+
"""Replace the labels of team-named columns (a ``KC`` column, a ``BUF`` column, ...) with their marks.
|
|
301
|
+
|
|
302
|
+
Args:
|
|
303
|
+
gt: A great_tables ``GT``.
|
|
304
|
+
columns: The columns whose labels to replace: a name, a list, a polars selector, or None for every column.
|
|
305
|
+
The column *names* are resolved (not labels set earlier with ``cols_label``).
|
|
306
|
+
league: The SDV league key, e.g. "nfl".
|
|
307
|
+
height: The image height in pixels.
|
|
308
|
+
season: One season whose marks to show; None for today's.
|
|
309
|
+
mark_type: "logo", "wordmark", or "headshot" (the column names are player ids).
|
|
310
|
+
id_system: The id system of the column names: for logos and wordmarks one of ``resolve``'s (None means
|
|
311
|
+
"auto"; NHL stats ids need "nhl_id"), for headshots "espn" or "gsis" as in ``headshot_url`` (None means
|
|
312
|
+
"espn").
|
|
313
|
+
strict: Raise UnresolvedTeamError instead of warning when a column name does not resolve to a team.
|
|
314
|
+
|
|
315
|
+
Returns:
|
|
316
|
+
GT: A new table; columns whose names do not resolve keep their labels, with one SdvplotWarning now.
|
|
317
|
+
|
|
318
|
+
Raises:
|
|
319
|
+
TypeError: If ``gt`` is not a great_tables GT.
|
|
320
|
+
InputError: (a ValueError) If ``mark_type`` is not "logo", "wordmark" or "headshot", ``height`` is not a number
|
|
321
|
+
of pixels of at least 1, ``league`` or ``id_system`` is unknown (for headshots: ``league`` has no ESPN
|
|
322
|
+
headshots, or ``id_system`` is not valid for it), or ``season`` is not one year or is outside the seasons
|
|
323
|
+
sdvplot knows for the league.
|
|
324
|
+
UnresolvedTeamError: (a ValueError) If ``strict=True`` and a column name does not resolve.
|
|
325
|
+
OfflineError: If the logo manifest (logos and wordmarks), or for "gsis" headshots the nflverse player table,
|
|
326
|
+
cannot be downloaded and no cached copy exists (a DownloadError, also an OSError, for an HTTP error status).
|
|
327
|
+
UnsafeDownloadError: (an OSError) If that download is refused: larger than the byte cap, past the deadline, or
|
|
328
|
+
redirected away from https.
|
|
329
|
+
|
|
330
|
+
Example:
|
|
331
|
+
::
|
|
332
|
+
|
|
333
|
+
from great_tables import GT
|
|
334
|
+
from sdvplot.great_tables import gt_sdv_cols_label
|
|
335
|
+
import polars as pl
|
|
336
|
+
|
|
337
|
+
df = pl.DataFrame({"KC": [12], "BUF": [10], "SF": [9]})
|
|
338
|
+
|
|
339
|
+
gt_sdv_cols_label(GT(df), ["KC", "BUF", "SF"], league="nfl")
|
|
340
|
+
|
|
341
|
+
See Also:
|
|
342
|
+
Ported from sdvplotR ``gt_sdv_cols_label()``: https://sdvplotR.sportsdataverse.org/reference/gt_sdv_cols_label.html
|
|
343
|
+
"""
|
|
344
|
+
_check_gt(gt)
|
|
345
|
+
h = check_px(height)
|
|
346
|
+
if mark_type not in KINDS:
|
|
347
|
+
raise InputError(f"mark_type must be one of {list(KINDS)}, got {mark_type!r}")
|
|
348
|
+
if id_system is None:
|
|
349
|
+
id_system = "espn" if mark_type == "headshot" else "auto"
|
|
350
|
+
names: list[str] = []
|
|
351
|
+
|
|
352
|
+
def record(name: str) -> str:
|
|
353
|
+
names.append(name)
|
|
354
|
+
return name
|
|
355
|
+
|
|
356
|
+
gt.cols_label_with(columns=columns, fn=record) # great_tables' own column selection; the result is discarded
|
|
357
|
+
imgs = mark_html(names, league=league, kind=mark_type, height=h, season=season, id_system=id_system,
|
|
358
|
+
strict=strict) # fmt: skip
|
|
359
|
+
cases: dict[str, Any] = {n: gt_html(img) for n, img in zip(names, imgs, strict=True) if img is not None}
|
|
360
|
+
return gt.cols_label(cases=cases) if cases else gt
|
|
361
|
+
|
|
362
|
+
|
|
363
|
+
def _constant(value: str) -> Callable[[Any], str]:
|
|
364
|
+
"""A great_tables ``fmt`` function that ignores the cell and returns ``value`` (sdvplotR's ``.constant``)."""
|
|
365
|
+
return lambda _: value
|
|
366
|
+
|
|
367
|
+
|
|
368
|
+
def _text(value: Any) -> str:
|
|
369
|
+
return "" if _missing(value) else html.escape(str(value))
|
|
370
|
+
|
|
371
|
+
|
|
372
|
+
def gt_merge_stack_team_color(
|
|
373
|
+
gt: GT,
|
|
374
|
+
col1: str,
|
|
375
|
+
col2: str,
|
|
376
|
+
team_col: str,
|
|
377
|
+
*,
|
|
378
|
+
league: str,
|
|
379
|
+
font_size_top: float = 14,
|
|
380
|
+
font_size_bottom: float = 12,
|
|
381
|
+
color: str = "black",
|
|
382
|
+
id_system: IdSystem = "auto",
|
|
383
|
+
strict: bool = False,
|
|
384
|
+
) -> GT:
|
|
385
|
+
"""Stack ``col1`` over ``col2`` in one cell: the top in bold small caps, the bottom in the team's primary color.
|
|
386
|
+
|
|
387
|
+
Args:
|
|
388
|
+
gt: A great_tables ``GT``.
|
|
389
|
+
col1: The column shown on top (bold small caps, in ``color``); it holds the merged cell.
|
|
390
|
+
col2: The column shown below, smaller and in the row's team color; it is hidden.
|
|
391
|
+
team_col: The column of teams whose primary colors color the bottom line.
|
|
392
|
+
league: The SDV league key, e.g. "nfl".
|
|
393
|
+
font_size_top: The top line's font size in pixels.
|
|
394
|
+
font_size_bottom: The bottom line's font size in pixels.
|
|
395
|
+
color: The top line's CSS color.
|
|
396
|
+
id_system: The id system of ``team_col``, as in ``resolve``: "auto" tries each in order; NHL stats ids need
|
|
397
|
+
"nhl_id".
|
|
398
|
+
strict: Raise UnresolvedTeamError instead of warning when a team does not resolve.
|
|
399
|
+
|
|
400
|
+
Returns:
|
|
401
|
+
GT: A new table. A team that does not resolve, or has no color, gets grey, with one SdvplotWarning.
|
|
402
|
+
|
|
403
|
+
Raises:
|
|
404
|
+
TypeError: If ``gt`` is not a great_tables GT.
|
|
405
|
+
InputError: (a ValueError) If ``league`` or ``id_system`` is unknown.
|
|
406
|
+
ValueError: If ``col1``, ``col2`` or ``team_col`` is not a column of the table's data.
|
|
407
|
+
UnresolvedTeamError: (a ValueError) If ``strict=True`` and a team does not resolve.
|
|
408
|
+
|
|
409
|
+
Example:
|
|
410
|
+
::
|
|
411
|
+
|
|
412
|
+
from great_tables import GT
|
|
413
|
+
from sdvplot.great_tables import gt_merge_stack_team_color
|
|
414
|
+
import polars as pl
|
|
415
|
+
|
|
416
|
+
df = pl.DataFrame({"team": ["KC", "BUF"], "mascot": ["Chiefs", "Bills"]})
|
|
417
|
+
|
|
418
|
+
gt_merge_stack_team_color(GT(df), "team", "mascot", "team", league="nfl")
|
|
419
|
+
|
|
420
|
+
See Also:
|
|
421
|
+
Ported from sdvplotR ``gt_merge_stack_team_color()``:
|
|
422
|
+
https://sdvplotR.sportsdataverse.org/reference/gt_merge_stack_team_color.html
|
|
423
|
+
"""
|
|
424
|
+
_check_gt(gt)
|
|
425
|
+
frame = nw.from_native(gt._tbl_data, eager_only=True) # the GT's own data, positional (spec T4)
|
|
426
|
+
for name in (col1, col2, team_col):
|
|
427
|
+
if name not in frame.columns:
|
|
428
|
+
raise ValueError(f"{name!r} is not a column of the table's data; columns are {frame.columns}")
|
|
429
|
+
colors = team_colors(league, frame[team_col].to_list(), id_system=id_system, strict=strict)
|
|
430
|
+
top_style = f"font-weight:bold;font-variant:small-caps;color:{color};font-size:{font_size_top}px"
|
|
431
|
+
for row, (top, bottom, team_color) in enumerate(
|
|
432
|
+
zip(frame[col1].to_list(), frame[col2].to_list(), colors, strict=True)
|
|
433
|
+
):
|
|
434
|
+
cell = (
|
|
435
|
+
f"<div style='line-height:{font_size_top - 2}px'><span style='{top_style}'>{_text(top)}</span></div>\n"
|
|
436
|
+
f"<div style='line-height:{font_size_bottom - 2}px'><span style='font-weight:bold;"
|
|
437
|
+
f"color:{team_color or 'grey'};font-size:{font_size_bottom}px'>{_text(bottom)}</span></div>"
|
|
438
|
+
)
|
|
439
|
+
# fmt() takes data rows, so row groups (which reorder the display) cannot shuffle the pairs
|
|
440
|
+
# one fmt() per row keeps it simple; group identical cells into one call if long tables get slow
|
|
441
|
+
gt = gt.fmt(_constant(cell), columns=col1, rows=[row])
|
|
442
|
+
return gt.cols_hide(col2)
|
|
443
|
+
|
|
444
|
+
|
|
445
|
+
# --- themes (R/gt_theme_sdv.R) ---
|
|
446
|
+
|
|
447
|
+
DENSITY: dict[str, dict[str, int]] = { # sdvplotR's .theme_density(): type sizes and row padding per density
|
|
448
|
+
"comfortable": {"body": 14, "pad": 6, "title": 26, "subtitle": 15, "label": 10, "group": 11, "source": 11},
|
|
449
|
+
"compact": {"body": 12, "pad": 3, "title": 22, "subtitle": 13, "label": 9, "group": 10, "source": 10},
|
|
450
|
+
"social": {"body": 17, "pad": 9, "title": 34, "subtitle": 19, "label": 12, "group": 13, "source": 13},
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
|
|
454
|
+
def _density(density: str) -> dict[str, int]:
|
|
455
|
+
"""The type and padding scale of ``density``; ValueError for an unknown one."""
|
|
456
|
+
if density not in DENSITY:
|
|
457
|
+
raise ValueError(f"density must be 'comfortable', 'compact' or 'social', not {density!r}")
|
|
458
|
+
return DENSITY[density]
|
|
459
|
+
|
|
460
|
+
|
|
461
|
+
# gt::default_fonts() (gt 1.3.0): the fallback stack R puts under every theme font
|
|
462
|
+
R_FONTS = (
|
|
463
|
+
"system-ui",
|
|
464
|
+
"Segoe UI",
|
|
465
|
+
"Roboto",
|
|
466
|
+
"Helvetica",
|
|
467
|
+
"Arial",
|
|
468
|
+
"sans-serif",
|
|
469
|
+
"Apple Color Emoji",
|
|
470
|
+
"Segoe UI Emoji",
|
|
471
|
+
"Segoe UI Symbol",
|
|
472
|
+
"Noto Color Emoji",
|
|
473
|
+
)
|
|
474
|
+
|
|
475
|
+
|
|
476
|
+
def _record(gt: GT, name: str, value: Any) -> GT:
|
|
477
|
+
"""A copy of ``gt`` carrying ``value`` as attribute ``name`` (``_sdvplot_scale``, ``_sdvplot_key``,
|
|
478
|
+
``_sdvplot_theme_fonts``); great_tables' own methods copy it along."""
|
|
479
|
+
out = copy.copy(gt)
|
|
480
|
+
out.__dict__[name] = value
|
|
481
|
+
return out
|
|
482
|
+
|
|
483
|
+
|
|
484
|
+
def _table_font(gt: GT, font: Any, weight: Any = None) -> GT:
|
|
485
|
+
"""R's ``opt_table_font(font = list(google_font(x), default_fonts()))`` for a theme's ``font`` (a GoogleFont).
|
|
486
|
+
|
|
487
|
+
As in R, the theme's fonts go in front of the table's, so fonts the caller set stay behind them (a browser falls
|
|
488
|
+
back font by font for each character it cannot draw, which is why gt lists emoji fonts after ``sans-serif``).
|
|
489
|
+
Unlike R, the run of fonts an earlier sdvplot theme put in front (recorded as ``_sdvplot_theme_fonts``) is taken
|
|
490
|
+
out first, so re-theming swaps it instead of stacking. great_tables writes each font once in the CSS.
|
|
491
|
+
"""
|
|
492
|
+
ours = [font.get_font_name(), *R_FONTS]
|
|
493
|
+
fonts = list(gt._options.table_font_names.value)
|
|
494
|
+
earlier = list(gt.__dict__.get("_sdvplot_theme_fonts", ()))
|
|
495
|
+
at = next(
|
|
496
|
+
(i for i in range(len(fonts) - len(earlier) + 1) if earlier and fonts[i : i + len(earlier)] == earlier), None
|
|
497
|
+
)
|
|
498
|
+
if at is not None: # still in the list (fonts the caller added since sit around it, untouched)
|
|
499
|
+
del fonts[at : at + len(earlier)]
|
|
500
|
+
out = gt.opt_table_font(font=[font, *R_FONTS, *fonts], add=False, weight=weight)
|
|
501
|
+
return _record(out, "_sdvplot_theme_fonts", ours)
|
|
502
|
+
|
|
503
|
+
|
|
504
|
+
# great_tables' default paddings that sdvplotR's density also scales (gt has the same defaults)
|
|
505
|
+
_DEFAULT_PADDING = {
|
|
506
|
+
"row_group_padding": 8,
|
|
507
|
+
"source_notes_padding": 4,
|
|
508
|
+
"summary_row_padding": 8,
|
|
509
|
+
"grand_summary_row_padding": 8,
|
|
510
|
+
}
|
|
511
|
+
SDV_NAVY, SDV_CYAN = "#0B1A33", "#7FE6DC"
|
|
512
|
+
SDV_HORIZON = "linear-gradient(90deg, #3346F0, #7FE6DC)"
|
|
513
|
+
|
|
514
|
+
|
|
515
|
+
def important(*styles: Any) -> Any:
|
|
516
|
+
"""great_tables cell styles as one inline rule with every declaration ``!important``. In VS Code and Positron
|
|
517
|
+
notebooks (great_tables' ``_repr_html_`` finds ``VSCODE_PID`` or ``POSITRON_VERSION`` and turns on its
|
|
518
|
+
``all_important``), every declaration of great_tables' own stylesheet is ``!important`` (``td, th {border-style:
|
|
519
|
+
none}``, the stub's, row groups' and stripes' ``background-color``), and a stylesheet ``!important`` beats a plain
|
|
520
|
+
inline style: without this, a border or fill shows in Jupyter, Quarto, Databricks and a saved image but not in VS
|
|
521
|
+
Code or Positron. Elsewhere the repr's stylesheet has no ``!important`` at all."""
|
|
522
|
+
rule = "".join(s._to_html_style() for s in styles)
|
|
523
|
+
return style.css(rule=" ".join(f"{d.strip()} !important;" for d in rule.split(";") if d.strip()))
|
|
524
|
+
|
|
525
|
+
|
|
526
|
+
def _warn_striped(gt: GT, helper: str, covered: str) -> None:
|
|
527
|
+
"""One SdvplotWarning when ``gt`` stripes its body rows and great_tables' repr here marks its stylesheet
|
|
528
|
+
``!important`` (VS Code and Positron; not Jupyter, Quarto, Databricks or saved files). The stripes' background and
|
|
529
|
+
text color then beat a plain inline style on every other row, such as ``data_color``'s fills, which sdvplot cannot
|
|
530
|
+
wrap in ``important()``."""
|
|
531
|
+
from great_tables._render import infer_render_env_defaults
|
|
532
|
+
|
|
533
|
+
if gt._options.row_striping_include_table_body.value and infer_render_env_defaults()["all_important"]:
|
|
534
|
+
warn(
|
|
535
|
+
f"{helper}: row striping is on, and in VS Code and Positron notebooks great_tables marks the stripes "
|
|
536
|
+
f"!important, so every other row shows the stripe's colors instead of the {covered} this draws; turn "
|
|
537
|
+
"striping off with opt_row_striping(row_striping=False)"
|
|
538
|
+
)
|
|
539
|
+
|
|
540
|
+
|
|
541
|
+
def _table_id(gt: GT) -> tuple[GT, str]:
|
|
542
|
+
"""The table's id, so CSS can be scoped to ``#id``, assigning a random one when it has none or an empty one
|
|
543
|
+
(sdvplotR's ``.table_id``; ``"#"`` alone would scope nothing)."""
|
|
544
|
+
table_id = gt._options.table_id.value
|
|
545
|
+
if table_id:
|
|
546
|
+
return gt, str(table_id)
|
|
547
|
+
table_id = random_id()
|
|
548
|
+
return gt.with_id(table_id), table_id
|
|
549
|
+
|
|
550
|
+
|
|
551
|
+
def _light_palette(horizon: str) -> dict[str, str]:
|
|
552
|
+
return {
|
|
553
|
+
"bg": "#FFFFFF",
|
|
554
|
+
"heading_bg": "#FFFFFF",
|
|
555
|
+
"title": SDV_NAVY,
|
|
556
|
+
"muted": "#4A5A75",
|
|
557
|
+
"label": "#16305C",
|
|
558
|
+
"text": SDV_NAVY,
|
|
559
|
+
"rule": "#E3E8F1",
|
|
560
|
+
"group_bg": "#EEF3FA",
|
|
561
|
+
"horizon": horizon,
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
|
|
565
|
+
def _dark_palette() -> dict[str, str]:
|
|
566
|
+
return {
|
|
567
|
+
"bg": SDV_NAVY,
|
|
568
|
+
"heading_bg": SDV_NAVY,
|
|
569
|
+
"title": "#FFFFFF",
|
|
570
|
+
"muted": "#A9B8D0",
|
|
571
|
+
"label": "#9CCBFF",
|
|
572
|
+
"text": "#EAEBEC",
|
|
573
|
+
"rule": "#1D3A66",
|
|
574
|
+
"group_bg": "#16305C",
|
|
575
|
+
"horizon": SDV_HORIZON,
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
|
|
579
|
+
def _background(gt: GT) -> str:
|
|
580
|
+
"""The table background as ``#rrggbb``, a best-effort read for picking ink: a translucent hex shows over the
|
|
581
|
+
(assumed white) page, so it is blended onto white; white when it is unset or not a hex color (a CSS name)."""
|
|
582
|
+
try:
|
|
583
|
+
return solid(str(gt._options.table_background_color.value))
|
|
584
|
+
except ValueError:
|
|
585
|
+
return "#ffffff"
|
|
586
|
+
|
|
587
|
+
|
|
588
|
+
def _secondary_on(bg: str, fg: str, target: float = 4.5) -> str:
|
|
589
|
+
"""sdvplotR's ``.theme_secondary_on()``: muted but legible, ``fg`` blended toward ``bg`` as far as still clears
|
|
590
|
+
``target`` contrast."""
|
|
591
|
+
for i in range(12):
|
|
592
|
+
# R's seq(0.45, 1, by = 0.05) is 0.45 + i * 0.05 unrounded (0.6000000000000001, ...): rounding the weight
|
|
593
|
+
# flips a channel's rounding for some colors
|
|
594
|
+
cand = mix(bg, fg, 0.45 + i * 0.05)
|
|
595
|
+
if contrast(cand, bg) >= target:
|
|
596
|
+
return cand
|
|
597
|
+
return fg
|
|
598
|
+
|
|
599
|
+
|
|
600
|
+
def _build_theme(gt: GT, pal: dict[str, str], density: str, tab_options: dict[str, Any]) -> GT:
|
|
601
|
+
"""sdvplotR's .sdv_theme_build(): fonts, styles, options and the scoped CSS, at ``density``."""
|
|
602
|
+
d = _density(density)
|
|
603
|
+
k = {role: d[role] / DENSITY["comfortable"][role] for role in DENSITY["comfortable"]}
|
|
604
|
+
|
|
605
|
+
def size(n: float, role: str) -> str:
|
|
606
|
+
return f"{round(n * k[role], 1):g}px"
|
|
607
|
+
|
|
608
|
+
gt, tid = _table_id(gt)
|
|
609
|
+
chivo, lato = google_font("Chivo"), google_font("Lato")
|
|
610
|
+
weight: Any = "800" # CSS numeric weights; great_tables types only the keywords
|
|
611
|
+
medium: Any = "500"
|
|
612
|
+
gt = (
|
|
613
|
+
_table_font(gt, lato)
|
|
614
|
+
.tab_style(style.text(font=chivo, weight=weight, size=size(22, "title"), color=pal["title"]), loc.title())
|
|
615
|
+
.tab_style(
|
|
616
|
+
style.text(font=lato, size=size(14, "subtitle"), color=pal.get("subtitle", pal["muted"])), loc.subtitle()
|
|
617
|
+
)
|
|
618
|
+
.tab_style(
|
|
619
|
+
style.text(font=chivo, weight=medium, size=size(13, "label"), color=pal["label"]), loc.column_labels()
|
|
620
|
+
)
|
|
621
|
+
.tab_style(
|
|
622
|
+
[
|
|
623
|
+
style.text(font=chivo, weight=medium, size=size(13, "group"), color=pal["label"]),
|
|
624
|
+
important(style.fill(color=pal["group_bg"])),
|
|
625
|
+
],
|
|
626
|
+
loc.row_groups(),
|
|
627
|
+
)
|
|
628
|
+
.tab_style(style.text(size=size(12, "source"), color=pal["muted"]), [loc.source_notes(), loc.footnotes()])
|
|
629
|
+
)
|
|
630
|
+
padding: dict[str, Any] = {}
|
|
631
|
+
if density != "comfortable": # sdvplotR scales great_tables' default paddings too
|
|
632
|
+
padding = {option: size(v, "pad") for option, v in _DEFAULT_PADDING.items()}
|
|
633
|
+
gt = gt.tab_options(
|
|
634
|
+
table_background_color=pal["bg"],
|
|
635
|
+
table_font_color=pal["text"],
|
|
636
|
+
table_font_size=size(15, "body"),
|
|
637
|
+
heading_align="left",
|
|
638
|
+
heading_background_color=pal["heading_bg"],
|
|
639
|
+
heading_padding=size(4, "pad"),
|
|
640
|
+
heading_border_bottom_style="none",
|
|
641
|
+
column_labels_background_color=pal["bg"],
|
|
642
|
+
column_labels_border_top_style="none",
|
|
643
|
+
column_labels_border_bottom_style="none",
|
|
644
|
+
column_labels_padding=size(6, "pad"),
|
|
645
|
+
table_body_hlines_color=pal["rule"],
|
|
646
|
+
table_body_hlines_width=px(1),
|
|
647
|
+
table_body_border_top_style="none",
|
|
648
|
+
table_body_border_bottom_style="none",
|
|
649
|
+
row_group_border_top_style="none",
|
|
650
|
+
row_group_border_bottom_style="none",
|
|
651
|
+
data_row_padding=size(7, "pad"),
|
|
652
|
+
table_border_top_style="none",
|
|
653
|
+
table_border_bottom_style="none",
|
|
654
|
+
source_notes_border_bottom_style="none", # great_tables 1.0 has no footnotes border options
|
|
655
|
+
**padding,
|
|
656
|
+
)
|
|
657
|
+
s = f"#{tid}"
|
|
658
|
+
css = [
|
|
659
|
+
# the horizon: one line under the column labels, drawn over the thead so a gradient spans the whole table
|
|
660
|
+
f"{s} thead {{position: relative;}}",
|
|
661
|
+
f'{s} thead::after {{content: ""; position: absolute; left: 0; right: 0; bottom: 0; height: 4px; '
|
|
662
|
+
f"background: {pal['horizon']};}}",
|
|
663
|
+
f"{s} .gt_col_headings th {{padding-bottom: 10px;}}",
|
|
664
|
+
# spanners as column labels (great_tables 1.0's loc.spanner_labels() needs explicit ids)
|
|
665
|
+
f"{s} .gt_column_spanner {{font-family: Chivo, sans-serif; font-weight: 500; font-size: {size(13, 'label')}; "
|
|
666
|
+
f"color: {pal['label']};}}",
|
|
667
|
+
# one 14px inset shared by the heading, the outer columns and the notes
|
|
668
|
+
f"{s} .gt_heading, {s} .gt_sourcenote, {s} .gt_footnote, {s} .gt_col_headings th:first-child, "
|
|
669
|
+
f"{s} tbody td:first-child {{padding-left: 14px !important;}}",
|
|
670
|
+
f"{s} .gt_heading, {s} .gt_sourcenote, {s} .gt_footnote, {s} .gt_col_headings th:last-child, "
|
|
671
|
+
f"{s} tbody td:last-child {{padding-right: 14px !important;}}",
|
|
672
|
+
f"{s} .gt_title {{padding-top: 12px !important;}}",
|
|
673
|
+
f"{s} .gt_subtitle {{padding-bottom: 12px !important;}}",
|
|
674
|
+
f"{s} tbody tr:last-child {{border-bottom: 2px solid {pal['bg']};}}", # no double rule under the last row
|
|
675
|
+
f"{s} td {{ font-variant-numeric: tabular-nums; }}", # digits line up between rows
|
|
676
|
+
]
|
|
677
|
+
gt = gt.opt_css("\n".join(css))
|
|
678
|
+
return gt.tab_options(**tab_options) if tab_options else gt # the caller's options last, so they win
|
|
679
|
+
|
|
680
|
+
|
|
681
|
+
def gt_theme_sdv(gt: GT, style: str = "light", density: str = "comfortable", **tab_options: Any) -> GT:
|
|
682
|
+
"""The SportsDataverse house table: Chivo labels, a Lato body, and the SDV gradient under the column labels.
|
|
683
|
+
|
|
684
|
+
Args:
|
|
685
|
+
gt: A great_tables ``GT``.
|
|
686
|
+
style: "light" (a white table) or "dark" (the SportsDataverse navy).
|
|
687
|
+
density: "comfortable" (as set), "compact" (smaller type and padding) or "social" (larger, for saved images).
|
|
688
|
+
**tab_options: Passed to ``GT.tab_options`` last, so they override the theme.
|
|
689
|
+
|
|
690
|
+
Returns:
|
|
691
|
+
GT: A new, themed table. The gradient line is CSS (``thead::after``) scoped to the table's id; an id is
|
|
692
|
+
assigned when the table has none.
|
|
693
|
+
|
|
694
|
+
Raises:
|
|
695
|
+
TypeError: If ``gt`` is not a great_tables GT.
|
|
696
|
+
ValueError: If ``style`` or ``density`` is unknown.
|
|
697
|
+
|
|
698
|
+
Example:
|
|
699
|
+
::
|
|
700
|
+
|
|
701
|
+
from great_tables import GT
|
|
702
|
+
from sdvplot.great_tables import gt_sdv_logos, gt_theme_sdv
|
|
703
|
+
import polars as pl
|
|
704
|
+
|
|
705
|
+
df = pl.DataFrame(
|
|
706
|
+
{
|
|
707
|
+
"team": ["KC", "BUF", "BAL"],
|
|
708
|
+
"espn_id": ["3139477", "3918298", "3916387"],
|
|
709
|
+
"wins": [12, 10, 9],
|
|
710
|
+
}
|
|
711
|
+
)
|
|
712
|
+
|
|
713
|
+
gt_theme_sdv(gt_sdv_logos(GT(df), "team", league="nfl").tab_header("AFC West"))
|
|
714
|
+
gt_theme_sdv(GT(df), style="dark", density="social")
|
|
715
|
+
|
|
716
|
+
See Also:
|
|
717
|
+
Ported from sdvplotR ``gt_theme_sdv()``: https://sdvplotR.sportsdataverse.org/reference/gt_theme_sdv.html
|
|
718
|
+
"""
|
|
719
|
+
_check_gt(gt)
|
|
720
|
+
if style not in ("light", "dark"):
|
|
721
|
+
raise ValueError(f"style must be 'light' or 'dark', got {style!r}")
|
|
722
|
+
pal = _dark_palette() if style == "dark" else _light_palette(SDV_HORIZON)
|
|
723
|
+
return _build_theme(gt, pal, density, tab_options)
|
|
724
|
+
|
|
725
|
+
|
|
726
|
+
def gt_theme_sdv_team(
|
|
727
|
+
gt: GT,
|
|
728
|
+
team: Any = None,
|
|
729
|
+
*,
|
|
730
|
+
league: str,
|
|
731
|
+
density: str = "comfortable",
|
|
732
|
+
id_system: IdSystem = "auto",
|
|
733
|
+
**tab_options: Any,
|
|
734
|
+
) -> GT:
|
|
735
|
+
"""``gt_theme_sdv`` in one team's colors: a title block in the primary color, the line in the secondary.
|
|
736
|
+
|
|
737
|
+
Ink on the title block is black or white, whichever reads better, and the subtitle is blended toward it while it
|
|
738
|
+
keeps 4.5:1 contrast. A secondary color too pale for a white table gives way to the primary for the line, and a
|
|
739
|
+
primary too light to read on white gives way to the SportsDataverse navy for the column labels.
|
|
740
|
+
|
|
741
|
+
Args:
|
|
742
|
+
gt: A great_tables ``GT``.
|
|
743
|
+
team: One team (an abbreviation, name or provider id); None for the SportsDataverse navy and cyan.
|
|
744
|
+
league: The SDV league key, e.g. "nfl".
|
|
745
|
+
density: "comfortable", "compact" or "social", as in ``gt_theme_sdv``.
|
|
746
|
+
id_system: The id system of ``team``, as in ``resolve``: "auto" tries each in order; NHL stats ids need
|
|
747
|
+
"nhl_id".
|
|
748
|
+
**tab_options: Passed to ``GT.tab_options`` last, so they override the theme.
|
|
749
|
+
|
|
750
|
+
Returns:
|
|
751
|
+
GT: A new, themed table. A team with no colors on file gets the SportsDataverse colors, with an
|
|
752
|
+
SdvplotWarning.
|
|
753
|
+
|
|
754
|
+
Raises:
|
|
755
|
+
TypeError: If ``gt`` is not a great_tables GT, or ``team`` is not one value.
|
|
756
|
+
InputError: (a ValueError) If ``team`` is given and ``league`` or ``id_system`` is unknown.
|
|
757
|
+
UnresolvedTeamError: (a ValueError) If ``team`` does not resolve to one team of ``league``.
|
|
758
|
+
ValueError: If ``density`` is unknown.
|
|
759
|
+
|
|
760
|
+
Example:
|
|
761
|
+
::
|
|
762
|
+
|
|
763
|
+
from great_tables import GT
|
|
764
|
+
from sdvplot.great_tables import gt_theme_sdv_team
|
|
765
|
+
import polars as pl
|
|
766
|
+
|
|
767
|
+
df = pl.DataFrame(
|
|
768
|
+
{
|
|
769
|
+
"team": ["KC", "BUF", "BAL"],
|
|
770
|
+
"espn_id": ["3139477", "3918298", "3916387"],
|
|
771
|
+
"wins": [12, 10, 9],
|
|
772
|
+
}
|
|
773
|
+
)
|
|
774
|
+
|
|
775
|
+
gt_theme_sdv_team(GT(df).tab_header("Chiefs leaders"), "KC", league="nfl")
|
|
776
|
+
|
|
777
|
+
See Also:
|
|
778
|
+
Ported from sdvplotR ``gt_theme_sdv_team()``:
|
|
779
|
+
https://sdvplotR.sportsdataverse.org/reference/gt_theme_sdv_team.html
|
|
780
|
+
"""
|
|
781
|
+
_check_gt(gt)
|
|
782
|
+
primary, secondary = SDV_NAVY, SDV_CYAN
|
|
783
|
+
if team is not None:
|
|
784
|
+
team_id = resolve(one_team(team, "gt_theme_sdv_team"), league, id_system=id_system, strict=True)
|
|
785
|
+
p, s = team_colors(league, team_id, which="primary"), team_colors(league, team_id, which="secondary")
|
|
786
|
+
if p:
|
|
787
|
+
primary, secondary = p, s or p
|
|
788
|
+
else:
|
|
789
|
+
warn(f"no colors on file for {league} team {team!r}; using the SportsDataverse colors")
|
|
790
|
+
title = on_color(primary)
|
|
791
|
+
pal = _light_palette(secondary if contrast(secondary, "#ffffff") >= 1.5 else primary)
|
|
792
|
+
pal.update(
|
|
793
|
+
heading_bg=primary,
|
|
794
|
+
title=title,
|
|
795
|
+
subtitle=_secondary_on(primary, title),
|
|
796
|
+
label=primary if contrast(primary, "#ffffff") >= 3 else SDV_NAVY,
|
|
797
|
+
)
|
|
798
|
+
return _build_theme(gt, pal, density, tab_options)
|