Skip to content

Commit 4677b24

Browse files
authored
Merge pull request #69 from posit-dev/feat-img-header
feat: `img_header()`
2 parents 73fd087 + 04e5e70 commit 4677b24

File tree

7 files changed

+213
-23
lines changed

7 files changed

+213
-23
lines changed

docs/_quarto.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ quartodoc:
7171
contents:
7272
- fa_icon_repeat
7373
- gt_fa_rating
74+
- img_header
7475

7576
- title: HTML and Formatting
7677
desc: "" # TODO: add desc

gt_extras/__init__.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@
2727

2828
from .formatting import fmt_pct_extra
2929

30+
from .images import img_header
31+
3032

3133
__all__ = [
3234
"gt_theme_538",
@@ -51,4 +53,5 @@
5153
"gt_hyperlink",
5254
"with_tooltip",
5355
"fmt_pct_extra",
56+
"img_header",
5457
]

gt_extras/images.py

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
from __future__ import annotations
2+
3+
from great_tables import html
4+
from great_tables._text import Html
5+
6+
__all__ = ["img_header"]
7+
8+
9+
def img_header(
10+
label: str,
11+
img_url: str,
12+
height: float = 60,
13+
font_size: int = 12,
14+
border_color: str = "black",
15+
text_color: str = "black",
16+
) -> Html:
17+
"""
18+
Create an HTML header with an image and a label, apt for a column label.
19+
20+
Parameters
21+
----------
22+
label
23+
The text label to display below the image.
24+
25+
img_url
26+
The URL of the image to display. This can be a filepath or an image on the web.
27+
28+
height
29+
The height of the image in pixels.
30+
31+
font_size
32+
The font size of the label text.
33+
34+
border_color
35+
The color of the border below the image.
36+
37+
text_color
38+
The color of the label text.
39+
40+
Returns
41+
-------
42+
html
43+
A Great Tables `html` element for the header.
44+
45+
Examples
46+
-------
47+
```{python}
48+
import pandas as pd
49+
from great_tables import GT, md
50+
import gt_extras as gte
51+
52+
df = pd.DataFrame(
53+
{
54+
"Category": ["Points", "Rebounds", "Assists", "Blocks", "Steals"],
55+
"Hart": [1051, 737, 453, 27, 119],
56+
"Brunson": [1690, 187, 475, 8, 60],
57+
"Bridges": [1444, 259, 306, 43, 75],
58+
}
59+
)
60+
61+
hart_header = gte.img_header(
62+
label="Josh Hart",
63+
img_url="https://a.espncdn.com/combiner/i?img=/i/headshots/nba/players/full/3062679.png",
64+
)
65+
66+
brunson_header = gte.img_header(
67+
label="Jalen Brunson",
68+
img_url="https://a.espncdn.com/combiner/i?img=/i/headshots/nba/players/full/3934672.png",
69+
)
70+
71+
bridges_header = gte.img_header(
72+
label="Mikal Bridges",
73+
img_url="https://a.espncdn.com/combiner/i?img=/i/headshots/nba/players/full/3147657.png",
74+
)
75+
76+
(
77+
GT(df, rowname_col="Category")
78+
.tab_source_note(md("Images and data courtesy of [ESPN](https://www.espn.com)"))
79+
.cols_label(
80+
{
81+
"Hart": hart_header,
82+
"Brunson": brunson_header,
83+
"Bridges": bridges_header,
84+
}
85+
)
86+
)
87+
```
88+
"""
89+
90+
img_html = f"""
91+
<img src="{img_url}" style="
92+
height:{height}px;
93+
border-bottom:2px solid {border_color};"
94+
/>
95+
""".strip()
96+
97+
label_html = f"""
98+
<div style="
99+
font-size:{font_size}px;
100+
color:{text_color};
101+
text-align:center;
102+
width:100%;
103+
">
104+
{label}
105+
</div>
106+
""".strip()
107+
108+
full_element = f"""
109+
<div style="text-align:center;">
110+
{img_html}
111+
{label_html}
112+
</div>
113+
""".strip()
114+
115+
return html(full_element)

gt_extras/plotting.py

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -336,7 +336,7 @@ def _make_dot_and_bar_html(
336336

337337
bar_container_style = "position:relative; top:1.2em;"
338338

339-
html = f'''
339+
html = f"""
340340
<div>
341341
<div style="{label_div_style}">
342342
{dot_category_label}
@@ -347,9 +347,9 @@ def _make_dot_and_bar_html(
347347
<div>{_make_bottom_bar_html(bar_val, fill=fill)}</div>
348348
</div>
349349
</div>
350-
'''
350+
""".strip()
351351

352-
return html.strip()
352+
return html
353353

354354
# Validate and get data column
355355
data_col_name, data_col_vals = _validate_and_get_single_column(
@@ -1400,7 +1400,7 @@ def _make_bar_stack_html(
14001400
font-size:{font_size}px;
14011401
color:{_ideal_fgnd_color(_html_color([color])[0])};
14021402
">{label}</div>
1403-
"""
1403+
""".strip()
14041404

14051405
bar_html = f"""
14061406
<div style="
@@ -1411,7 +1411,7 @@ def _make_bar_stack_html(
14111411
height:{height}px;
14121412
background:{color};
14131413
">{label_html}</div>
1414-
"""
1414+
""".strip()
14151415
if value != 0 and not is_na(gt._tbl_data, value):
14161416
bars_html.append(bar_html.strip())
14171417
current_left += bar_width + spacing
@@ -1420,8 +1420,9 @@ def _make_bar_stack_html(
14201420
<div style="position:relative; width:{width}px; height:{height}px;">
14211421
{"".join(bars_html)}
14221422
</div>
1423-
"""
1424-
return html.strip()
1423+
""".strip()
1424+
1425+
return html
14251426

14261427
# Throw if `scale_type` is not one of the allowed values
14271428
if scale_type not in ["relative", "absolute"]:
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
# serializer version: 1
2+
# name: test_img_header_snapshot
3+
Html(text='<div style="text-align:center;">\n <img src="https://example.com/image.png" style="\n height:60px;\n border-bottom:2px solid black;"\n />\n <div style="\n font-size:12px;\n color:black;\n text-align:center;\n width:100%;\n ">\n Test Label\n </div>\n </div>')
4+
# ---

gt_extras/tests/__snapshots__/test_plotting.ambr

Lines changed: 8 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -51,32 +51,28 @@
5151
width:32.666666666666664px;
5252
height:30px;
5353
background:red;
54-
">
55-
<div style="
54+
"><div style="
5655
position:absolute;
5756
left:50%;
5857
top:50%;
5958
transform:translateX(-50%) translateY(-50%);
6059
font-size:10px;
6160
color:#000000;
62-
">10</div>
63-
</div><div style="
61+
">10</div></div><div style="
6462
position:absolute;
6563
left:34.666666666666664px;
6664
top:0px;
6765
width:65.33333333333333px;
6866
height:30px;
6967
background:blue;
70-
">
71-
<div style="
68+
"><div style="
7269
position:absolute;
7370
left:50%;
7471
top:50%;
7572
transform:translateX(-50%) translateY(-50%);
7673
font-size:10px;
7774
color:#FFFFFF;
78-
">20</div>
79-
</div>
75+
">20</div></div>
8076
</div></td>
8177
</tr>
8278
<tr>
@@ -89,32 +85,28 @@
8985
width:56.0px;
9086
height:30px;
9187
background:red;
92-
">
93-
<div style="
88+
"><div style="
9489
position:absolute;
9590
left:50%;
9691
top:50%;
9792
transform:translateX(-50%) translateY(-50%);
9893
font-size:10px;
9994
color:#000000;
100-
">40</div>
101-
</div><div style="
95+
">40</div></div><div style="
10296
position:absolute;
10397
left:58.0px;
10498
top:0px;
10599
width:42.0px;
106100
height:30px;
107101
background:blue;
108-
">
109-
<div style="
102+
"><div style="
110103
position:absolute;
111104
left:50%;
112105
top:50%;
113106
transform:translateX(-50%) translateY(-50%);
114107
font-size:10px;
115108
color:#FFFFFF;
116-
">30</div>
117-
</div>
109+
">30</div></div>
118110
</div></td>
119111
</tr>
120112
</tbody>

gt_extras/tests/test_images.py

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
from great_tables._text import Html
2+
from gt_extras.images import img_header
3+
4+
5+
def test_img_header_snapshot(snapshot):
6+
result = img_header(label="Test Label", img_url="https://example.com/image.png")
7+
assert snapshot == result
8+
9+
10+
def test_img_header_basic():
11+
result = img_header(label="Test Label", img_url="https://example.com/image.png")
12+
13+
assert isinstance(result, Html)
14+
assert "Test Label" in result.text
15+
assert "https://example.com/image.png" in result.text
16+
assert "height:60px;" in result.text
17+
assert "border-bottom:2px solid black;" in result.text
18+
assert "color:black;" in result.text
19+
20+
21+
def test_img_header_custom_height_and_colors():
22+
result = img_header(
23+
label="Custom Label",
24+
img_url="https://example.com/custom.png",
25+
height=100,
26+
border_color="blue",
27+
text_color="red",
28+
)
29+
30+
assert isinstance(result, Html)
31+
assert "Custom Label" in result.text
32+
assert "https://example.com/custom.png" in result.text
33+
assert "height:100px;" in result.text
34+
assert "border-bottom:2px solid blue;" in result.text
35+
assert "color:red;" in result.text
36+
37+
38+
def test_img_header_custom_font_size():
39+
result = img_header(
40+
label="Font Size Test", img_url="https://example.com/font.png", font_size=20
41+
)
42+
43+
assert isinstance(result, Html)
44+
assert "Font Size Test" in result.text
45+
assert "font-size:20px;" in result.text
46+
47+
48+
def test_img_header_empty_label():
49+
result = img_header(label="", img_url="https://example.com/empty_label.png")
50+
51+
assert isinstance(result, Html)
52+
assert "https://example.com/empty_label.png" in result.text
53+
assert "<div" in result.text
54+
assert "font-size:12px;" in result.text
55+
56+
57+
def test_img_header_empty_url():
58+
result = img_header(label="Invalid URL Test", img_url="")
59+
60+
assert isinstance(result, Html)
61+
assert "Invalid URL Test" in result.text
62+
assert 'src=""' in result.text
63+
64+
65+
def test_img_header_no_border():
66+
result = img_header(
67+
label="No Border Test",
68+
img_url="https://example.com/no_border.png",
69+
border_color="transparent",
70+
)
71+
72+
assert isinstance(result, Html)
73+
assert "No Border Test" in result.text
74+
assert "border-bottom:2px solid transparent;" in result.text

0 commit comments

Comments
 (0)