From e7ff8b16afd7bff0f8401c4edd6802e52a4d0ee5 Mon Sep 17 00:00:00 2001
From: Jules <54960783+juleswg23@users.noreply.github.com>
Date: Fri, 15 Aug 2025 17:10:11 -0400
Subject: [PATCH 01/10] initial setup
---
docs/_quarto.yml | 1 +
gt_extras/__init__.py | 3 ++-
gt_extras/images.py | 20 ++++++++++++++++++--
3 files changed, 21 insertions(+), 3 deletions(-)
diff --git a/docs/_quarto.yml b/docs/_quarto.yml
index 00f68dfa..cc7eb05b 100644
--- a/docs/_quarto.yml
+++ b/docs/_quarto.yml
@@ -91,6 +91,7 @@ quartodoc:
contents:
- add_text_img
- fa_icon_repeat
+ - fmt_image_circle
- gt_fa_rank_change
- gt_fa_rating
- img_header
diff --git a/gt_extras/__init__.py b/gt_extras/__init__.py
index 5671f6e4..aa77a973 100644
--- a/gt_extras/__init__.py
+++ b/gt_extras/__init__.py
@@ -9,7 +9,7 @@
from .formatting import fmt_pct_extra, gt_duplicate_column, gt_two_column_layout
from .html import gt_merge_stack, with_hyperlink, with_tooltip
from .icons import fa_icon_repeat, gt_fa_rank_change, gt_fa_rating
-from .images import add_text_img, img_header
+from .images import add_text_img, fmt_image_circle, img_header
from .plotting import (
gt_plt_bar,
gt_plt_bar_pct,
@@ -68,6 +68,7 @@
"gt_two_column_layout",
"add_text_img",
"img_header",
+ "fmt_image_circle",
"gt_add_divider",
"gt_plt_summary",
]
diff --git a/gt_extras/images.py b/gt_extras/images.py
index 36d388b0..03aace78 100644
--- a/gt_extras/images.py
+++ b/gt_extras/images.py
@@ -1,10 +1,11 @@
from __future__ import annotations
-from great_tables import html
+from great_tables import GT, html
from great_tables._helpers import px
+from great_tables._tbl_data import SelectExpr
from great_tables._text import Html
-__all__ = ["add_text_img", "img_header"]
+__all__ = ["add_text_img", "img_header", "fmt_image_circle"]
def img_header(
@@ -244,3 +245,18 @@ def add_text_img(
""".strip()
return combined_html
+
+
+def fmt_image_circle(
+ gt: GT,
+ columns: SelectExpr,
+ color: str = "grey",
+ height: str | int | None = None,
+ width: str | None = None,
+ border_radius: str | None = "50%",
+ border_width: int = 3,
+ border_color: str = "black",
+ border_style: str = "solid",
+) -> GT:
+ res = gt
+ return res
From d949c898db539530cc63451f45577301d3a6f531 Mon Sep 17 00:00:00 2001
From: Jules <54960783+juleswg23@users.noreply.github.com>
Date: Fri, 15 Aug 2025 17:17:26 -0400
Subject: [PATCH 02/10] rename to fmt_img_circle
---
docs/_quarto.yml | 2 +-
gt_extras/__init__.py | 4 ++--
gt_extras/images.py | 4 ++--
3 files changed, 5 insertions(+), 5 deletions(-)
diff --git a/docs/_quarto.yml b/docs/_quarto.yml
index cc7eb05b..c4b36adc 100644
--- a/docs/_quarto.yml
+++ b/docs/_quarto.yml
@@ -91,7 +91,7 @@ quartodoc:
contents:
- add_text_img
- fa_icon_repeat
- - fmt_image_circle
+ - fmt_img_circle
- gt_fa_rank_change
- gt_fa_rating
- img_header
diff --git a/gt_extras/__init__.py b/gt_extras/__init__.py
index aa77a973..2f233602 100644
--- a/gt_extras/__init__.py
+++ b/gt_extras/__init__.py
@@ -9,7 +9,7 @@
from .formatting import fmt_pct_extra, gt_duplicate_column, gt_two_column_layout
from .html import gt_merge_stack, with_hyperlink, with_tooltip
from .icons import fa_icon_repeat, gt_fa_rank_change, gt_fa_rating
-from .images import add_text_img, fmt_image_circle, img_header
+from .images import add_text_img, fmt_img_circle, img_header
from .plotting import (
gt_plt_bar,
gt_plt_bar_pct,
@@ -68,7 +68,7 @@
"gt_two_column_layout",
"add_text_img",
"img_header",
- "fmt_image_circle",
+ "fmt_img_circle",
"gt_add_divider",
"gt_plt_summary",
]
diff --git a/gt_extras/images.py b/gt_extras/images.py
index 03aace78..6a8d541a 100644
--- a/gt_extras/images.py
+++ b/gt_extras/images.py
@@ -5,7 +5,7 @@
from great_tables._tbl_data import SelectExpr
from great_tables._text import Html
-__all__ = ["add_text_img", "img_header", "fmt_image_circle"]
+__all__ = ["add_text_img", "img_header", "fmt_img_circle"]
def img_header(
@@ -247,7 +247,7 @@ def add_text_img(
return combined_html
-def fmt_image_circle(
+def fmt_img_circle(
gt: GT,
columns: SelectExpr,
color: str = "grey",
From 366436b3f593259d858e962426877f7bc79f32f7 Mon Sep 17 00:00:00 2001
From: Jules <54960783+juleswg23@users.noreply.github.com>
Date: Mon, 18 Aug 2025 10:41:40 -0400
Subject: [PATCH 03/10] add jerry's code
---
gt_extras/images.py | 350 +++++++++++++++++++++++++++++++++++++++++++-
1 file changed, 342 insertions(+), 8 deletions(-)
diff --git a/gt_extras/images.py b/gt_extras/images.py
index 6a8d541a..b1c4151b 100644
--- a/gt_extras/images.py
+++ b/gt_extras/images.py
@@ -1,9 +1,16 @@
from __future__ import annotations
+import re
+from dataclasses import dataclass
+from pathlib import Path
+from typing import Any, ClassVar
+
from great_tables import GT, html
+from great_tables._gt_data import FormatFns
from great_tables._helpers import px
-from great_tables._tbl_data import SelectExpr
+from great_tables._tbl_data import Agnostic, DataFrameLike, PlExpr, SelectExpr, is_na
from great_tables._text import Html
+from great_tables._utils import is_valid_http_schema
__all__ = ["add_text_img", "img_header", "fmt_img_circle"]
@@ -249,14 +256,341 @@ def add_text_img(
def fmt_img_circle(
gt: GT,
- columns: SelectExpr,
- color: str = "grey",
+ columns: SelectExpr = None,
+ rows: int | list[int] | None = None,
height: str | int | None = None,
width: str | None = None,
border_radius: str | None = "50%",
- border_width: int = 3,
- border_color: str = "black",
- border_style: str = "solid",
+ border_width: str | int | None = None,
+ border_color: str | None = None,
+ border_style: str | None = None,
+ sep: str = " ",
+ path: str | Path | None = None,
+ file_pattern: str = "{}",
+ encode: bool = True,
) -> GT:
- res = gt
- return res
+ """Format image paths to generate circular images within table cells.
+ `fmt_img_circle()` is a utility function similar to [`fmt_image()`](`great_tables.fmt_image`),
+ but it also accepts additional parameters for customizing the image border:
+ `border_radius=`, `border_width=`, `border_color=`, and `border_style=`.
+
+ When calling `fmt_img_circle()`, **Great Tables** automatically sets `border_radius="50%"` to
+ create a full circle. However, we can't assume whether you want the border to be visible.
+ Therefore, you should supply at least one of the following: `border_width=`, `border_color=`,
+ or `border_style=`. Based on your input, sensible defaults will be applied for any unset border
+ properties.
+
+ Parameters
+ ----------
+ columns
+ The columns to target. Can either be a single column name or a series of column names
+ provided in a list.
+ rows
+ In conjunction with `columns=`, we can specify which of their rows should undergo
+ formatting. The default is all rows, resulting in all rows in targeted columns being
+ formatted. Alternatively, we can supply a list of row indices.
+ height
+ The height of the rendered images.
+ width
+ The width of the rendered images.
+ border_radius
+ The radius of the image border. Accepts values in pixels (`px`) or percentages (`%`).
+ Defaults to `50%` to create a circular image.
+ border_width
+ The width of the image border.
+ border_color
+ The color of the image border.
+ border_style
+ The style of the image border (e.g., solid, dashed, dotted).
+ sep
+ In the output of images within a body cell, `sep=` provides the separator between each
+ image.
+ path
+ An optional path to local image files or an HTTP/HTTPS URL.
+ This is combined with the filenames to form the complete image paths.
+ file_pattern
+ The pattern to use for mapping input values in the body cells to the names of the graphics
+ files. The string supplied should use `"{}"` in the pattern to map filename fragments to
+ input strings.
+ encode
+ The option to always use Base64 encoding for image paths that are determined to be local. By
+ default, this is `True`.
+
+ Returns
+ -------
+ GT
+ The GT object is returned. This is the same object that the method is called on so that we
+ can facilitate method chaining.
+
+ Examples
+ --------
+ ```{python}
+ import polars as pl
+ from great_tables import GT, vals, html
+
+ posit_avatar = "https://avatars.githubusercontent.com/u/107264312?s=200&v=4"
+ rich_avatar = "https://avatars.githubusercontent.com/u/5612024?v=4"
+ michael_avatar = "https://avatars.githubusercontent.com/u/2574498?v=4"
+
+ title_img = vals.fmt_image_circle(posit_avatar, height=100, border_color="#D3D3D3")[0]
+ df = pl.DataFrame({"@rich-iannone": [rich_avatar], "@machow": [michael_avatar]})
+
+ (
+ GT(df)
+ .fmt_image_circle(height=150, border_width=5)
+ .tab_header(html(title_img))
+ .cols_align("center")
+ .opt_stylize(color="green", style=6)
+ )
+ ```
+ """
+ default_border_props = {
+ "border-width": "3px",
+ "border-color": "#0A0A0A",
+ "border-style": "solid",
+ }
+
+ border_props = {
+ "border-width": border_width,
+ "border-color": border_color,
+ "border-style": border_style,
+ }
+
+ # This block assigns default values to `border-width`, `border-color`, and `border-style`
+ # if the user specifies at least one of them but leaves others unset.
+ if any(border_props.values()):
+ for k, v in default_border_props.items():
+ if border_props[k] is None:
+ border_props[k] = v
+
+ border_width, border_color, border_style = border_props.values()
+
+ return _fmt_image(
+ gt,
+ columns=columns,
+ rows=rows,
+ height=height,
+ width=width,
+ border_radius=border_radius,
+ border_width=border_width,
+ border_color=border_color,
+ border_style=border_style,
+ sep=sep,
+ path=path,
+ file_pattern=file_pattern,
+ encode=encode,
+ )
+
+
+def _fmt_image(
+ gt: GT,
+ columns: SelectExpr = None,
+ rows: int | list[int] | None = None,
+ height: str | int | None = None,
+ width: str | None = None,
+ border_radius: str | None = None,
+ border_width: str | int | None = None,
+ border_color: str | None = None,
+ border_style: str | None = None,
+ sep: str = " ",
+ path: str | Path | None = None,
+ file_pattern: str = "{}",
+ encode: bool = True,
+) -> GT:
+ # TODO: most parameter options should allow a polars expression (or from_column) ----
+ # can other fmt functions do this kind of thing?
+ expr_cols = [height, width, sep, path, file_pattern, encode]
+
+ if any(isinstance(x, PlExpr) for x in expr_cols):
+ raise NotImplementedError(
+ "fmt_image currently does not support polars expressions for arguments other than"
+ " columns and rows"
+ )
+
+ if height is None and width is None:
+ height = "2em"
+
+ formatter = FmtImage(
+ gt._tbl_data,
+ height=height,
+ width=width,
+ border_radius=border_radius,
+ border_width=border_width,
+ border_color=border_color,
+ border_style=border_style,
+ sep=sep,
+ path=path,
+ file_pattern=file_pattern,
+ encode=encode,
+ )
+ return GT.fmt(
+ gt,
+ fns=FormatFns(
+ html=formatter.to_html, latex=formatter.to_latex, default=formatter.to_html
+ ),
+ columns=columns,
+ rows=rows,
+ )
+
+
+@dataclass
+class FmtImage:
+ dispatch_on: DataFrameLike | Agnostic = Agnostic()
+ height: str | int | None = None
+ width: str | None = None
+ border_radius: str | None = None
+ border_width: str | int | None = None
+ border_color: str | None = None
+ border_style: str | None = None
+ sep: str = " "
+ path: str | Path | None = None
+ file_pattern: str = "{}"
+ encode: bool = True
+ SPAN_TEMPLATE: ClassVar = '{}'
+
+ def to_html(self, val: Any):
+ # TODO: are we assuming val is a string? (or coercing?)
+
+ # otherwise...
+
+ if is_na(self.dispatch_on, val):
+ return val
+
+ if "," in val:
+ files = re.split(r",\s*", val)
+ else:
+ files = [val]
+
+ # TODO: if we allowing height and width to be set based on column values, then
+ # they could end up as bespoke types like np int64, etc..
+ # We should ensure we process those before hitting FmtImage
+ if isinstance(self.height, (int, float)):
+ height = px(self.height)
+ else:
+ height = self.height
+
+ # TODO: note that only height can be numeric in the R program. Is this on purpose?
+ # In any event, raising explicitly for numeric width below.
+ if isinstance(self.width, (int, float)):
+ raise NotImplementedError(
+ "The `width=` argument must be specified as a string."
+ )
+ else:
+ width = self.width
+
+ if self.border_radius is not None:
+ if not isinstance(self.border_radius, str):
+ raise NotImplementedError(
+ "The `border_radius=` argument must be specified as a string."
+ )
+ if not any(self.border_radius.endswith(suffix) for suffix in {"px", "%"}):
+ raise NotImplementedError(
+ 'The `border_radius=` argument must end with either "px" or "%"'
+ )
+
+ border_radius = self.border_radius
+
+ if isinstance(self.border_width, (int, float)):
+ border_width = px(self.border_width)
+ else:
+ border_width = self.border_width
+
+ border_color = self.border_color
+ border_style = self.border_style
+
+ full_files = self._apply_pattern(self.file_pattern, files)
+
+ out: list[str] = []
+ for file in full_files:
+ # Case 1: from url via `dispatch_on`
+ if self.path is None and is_valid_http_schema(file):
+ uri = file.rstrip().removesuffix("/")
+ # Case 2: from url via `path`
+ elif self.path is not None and is_valid_http_schema(str(self.path)):
+ norm_path = str(self.path).rstrip().removesuffix("/")
+ uri = f"{norm_path}/{file}"
+ # Case 3:
+ else:
+ filename = str((Path(self.path or "") / file).expanduser().absolute())
+
+ if self.encode:
+ uri = self._get_image_uri(filename)
+ else:
+ uri = filename
+
+ # TODO: do we have a way to create tags, that is good at escaping, etc..?
+ out.append(
+ self._build_img_tag(
+ uri=uri,
+ height=height,
+ width=width,
+ border_radius=border_radius,
+ border_width=border_width,
+ border_color=border_color,
+ border_style=border_style,
+ )
+ )
+
+ img_tags = self.sep.join(out)
+ span = self.SPAN_TEMPLATE.format(img_tags)
+
+ return span
+
+ def to_latex(self, val: Any):
+ from warnings import warn
+
+ from ._gt_data import FormatterSkipElement
+
+ warn("fmt_image() is not currently implemented in LaTeX output.")
+
+ return FormatterSkipElement()
+
+ @staticmethod
+ def _apply_pattern(file_pattern: str, files: list[str]) -> list[str]:
+ return [file_pattern.format(file) for file in files]
+
+ @classmethod
+ def _get_image_uri(cls, filename: str) -> str:
+ import base64
+
+ with open(filename, "rb") as f:
+ encoded = base64.b64encode(f.read()).decode()
+
+ mime_type = cls._get_mime_type(filename)
+
+ return f"data:{mime_type};base64,{encoded}"
+
+ @staticmethod
+ def _get_mime_type(filename: str) -> str:
+ # note that we strip off the leading "."
+ suffix = Path(filename).suffix[1:]
+
+ if suffix == "svg":
+ return "image/svg+xml"
+ elif suffix == "jpg":
+ return "image/jpeg"
+
+ return f"image/{suffix}"
+
+ @staticmethod
+ def _build_img_tag(
+ uri: str,
+ height: str | None = None,
+ width: str | None = None,
+ border_radius: str | None = None,
+ border_width: str | None = None,
+ border_color: str | None = None,
+ border_style: str | None = None,
+ ) -> str:
+ style_string = "".join(
+ [
+ f"height: {height};" if height is not None else "",
+ f"width: {width};" if width is not None else "",
+ f"border-radius: {border_radius};" if border_radius is not None else "",
+ f"border-width: {border_width};" if border_width is not None else "",
+ f"border-color: {border_color};" if border_color is not None else "",
+ f"border-style: {border_style};" if border_style is not None else "",
+ "vertical-align: middle;",
+ ]
+ )
+ return f''
From f7f556f164811af8232c7729a6d02869a4549422 Mon Sep 17 00:00:00 2001
From: Jules <54960783+juleswg23@users.noreply.github.com>
Date: Mon, 18 Aug 2025 11:02:57 -0400
Subject: [PATCH 04/10] update fn name and example fns
---
docs/_quarto.yml | 2 +-
docs/examples/with-code.qmd | 24 ++++++++++++++++++++++++
gt_extras/__init__.py | 4 ++--
gt_extras/images.py | 19 ++++++++++---------
4 files changed, 37 insertions(+), 12 deletions(-)
diff --git a/docs/_quarto.yml b/docs/_quarto.yml
index c4b36adc..c2eacd79 100644
--- a/docs/_quarto.yml
+++ b/docs/_quarto.yml
@@ -91,9 +91,9 @@ quartodoc:
contents:
- add_text_img
- fa_icon_repeat
- - fmt_img_circle
- gt_fa_rank_change
- gt_fa_rating
+ - gt_fmt_img_circle
- img_header
- title: Utilities
diff --git a/docs/examples/with-code.qmd b/docs/examples/with-code.qmd
index a3a4f5a1..8a6825ec 100644
--- a/docs/examples/with-code.qmd
+++ b/docs/examples/with-code.qmd
@@ -921,6 +921,30 @@ df = pd.DataFrame({
```
{{< include ./_show-last.qmd >}}
+
+### [gt_fmt_img_circle](https://posit-dev.github.io/gt-extras/reference/gt_fmt_img_circle.html){title="Click for reference"}
+```{python}
+# | label: gt_fmt_img_circle
+import polars as pl
+from great_tables import GT, vals, html
+import gt_extras as gte
+
+rich_avatar = "https://avatars.githubusercontent.com/u/5612024?v=4"
+michael_avatar = "https://avatars.githubusercontent.com/u/2574498?v=4"
+jules_avatar = "https://avatars.githubusercontent.com/u/54960783?v=4"
+
+
+df = pl.DataFrame({"@machow": [michael_avatar], "@rich-iannone": [rich_avatar], "@juleswg23": [jules_avatar]})
+
+(
+ GT(df)
+ .cols_align("center")
+ .opt_stylize(color="green", style=6)
+ .pipe(gte.gt_fmt_img_circle, height=80)
+)
+```
+{{< include ./_show-last.qmd >}}
+
### [gt_fa_rank_change](https://posit-dev.github.io/gt-extras/reference/gt_fa_rank_change.html){title="Click for reference"}
```{python}
# | label: gt_fa_rank_change
diff --git a/gt_extras/__init__.py b/gt_extras/__init__.py
index 2f233602..45ea3681 100644
--- a/gt_extras/__init__.py
+++ b/gt_extras/__init__.py
@@ -9,7 +9,7 @@
from .formatting import fmt_pct_extra, gt_duplicate_column, gt_two_column_layout
from .html import gt_merge_stack, with_hyperlink, with_tooltip
from .icons import fa_icon_repeat, gt_fa_rank_change, gt_fa_rating
-from .images import add_text_img, fmt_img_circle, img_header
+from .images import add_text_img, gt_fmt_img_circle, img_header
from .plotting import (
gt_plt_bar,
gt_plt_bar_pct,
@@ -68,7 +68,7 @@
"gt_two_column_layout",
"add_text_img",
"img_header",
- "fmt_img_circle",
+ "gt_fmt_img_circle",
"gt_add_divider",
"gt_plt_summary",
]
diff --git a/gt_extras/images.py b/gt_extras/images.py
index b1c4151b..f130b3ca 100644
--- a/gt_extras/images.py
+++ b/gt_extras/images.py
@@ -12,7 +12,7 @@
from great_tables._text import Html
from great_tables._utils import is_valid_http_schema
-__all__ = ["add_text_img", "img_header", "fmt_img_circle"]
+__all__ = ["add_text_img", "img_header", "gt_fmt_img_circle"]
def img_header(
@@ -254,7 +254,8 @@ def add_text_img(
return combined_html
-def fmt_img_circle(
+# Copied from https://github.com/posit-dev/great-tables/pull/676
+def gt_fmt_img_circle(
gt: GT,
columns: SelectExpr = None,
rows: int | list[int] | None = None,
@@ -270,11 +271,11 @@ def fmt_img_circle(
encode: bool = True,
) -> GT:
"""Format image paths to generate circular images within table cells.
- `fmt_img_circle()` is a utility function similar to [`fmt_image()`](`great_tables.fmt_image`),
+ `gt_fmt_img_circle()` is a utility function similar to [`fmt_image()`](`great_tables.fmt_image`),
but it also accepts additional parameters for customizing the image border:
`border_radius=`, `border_width=`, `border_color=`, and `border_style=`.
- When calling `fmt_img_circle()`, **Great Tables** automatically sets `border_radius="50%"` to
+ When calling `gt_fmt_img_circle()`, **Great Tables** automatically sets `border_radius="50%"` to
create a full circle. However, we can't assume whether you want the border to be visible.
Therefore, you should supply at least one of the following: `border_width=`, `border_color=`,
or `border_style=`. Based on your input, sensible defaults will be applied for any unset border
@@ -327,20 +328,20 @@ def fmt_img_circle(
```{python}
import polars as pl
from great_tables import GT, vals, html
+ import gt_extras as gte
- posit_avatar = "https://avatars.githubusercontent.com/u/107264312?s=200&v=4"
rich_avatar = "https://avatars.githubusercontent.com/u/5612024?v=4"
michael_avatar = "https://avatars.githubusercontent.com/u/2574498?v=4"
+ jules_avatar = "https://avatars.githubusercontent.com/u/54960783?v=4"
+
- title_img = vals.fmt_image_circle(posit_avatar, height=100, border_color="#D3D3D3")[0]
- df = pl.DataFrame({"@rich-iannone": [rich_avatar], "@machow": [michael_avatar]})
+ df = pl.DataFrame({"@machow": [michael_avatar], "@rich-iannone": [rich_avatar], "@juleswg23": [jules_avatar]})
(
GT(df)
- .fmt_image_circle(height=150, border_width=5)
- .tab_header(html(title_img))
.cols_align("center")
.opt_stylize(color="green", style=6)
+ .pipe(gte.gt_fmt_img_circle, height=80)
)
```
"""
From 924354628934b4da4f7802332bc31aea27a204b3 Mon Sep 17 00:00:00 2001
From: Jules <54960783+juleswg23@users.noreply.github.com>
Date: Mon, 18 Aug 2025 11:06:40 -0400
Subject: [PATCH 05/10] re-order with-code. Add examples
---
docs/examples/index.qmd | 5 ++++
docs/examples/with-code.qmd | 47 ++++++++++++++++++-------------------
gt_extras/images.py | 2 +-
3 files changed, 29 insertions(+), 25 deletions(-)
diff --git a/docs/examples/index.qmd b/docs/examples/index.qmd
index 2bd0843c..55c803ce 100644
--- a/docs/examples/index.qmd
+++ b/docs/examples/index.qmd
@@ -293,6 +293,11 @@ addEventListener('resize', setTableWidths);
[gt_fa_rating](./with-code.html#gt_fa_rating){.embed-hover-overlay}
:::
+:::{.masonry-item style="--width: 304;"}
+{{< embed with-code.qmd#gt_fmt_img_circle >}}
+[gt_fmt_img_circle](./with-code.html#gt_fmt_img_circle){.embed-hover-overlay}
+:::
+
:::{.masonry-item style="--width: 355;"}
{{< embed with-code.qmd#img_header >}}
[img_header](./with-code.html#img_header){.embed-hover-overlay}
diff --git a/docs/examples/with-code.qmd b/docs/examples/with-code.qmd
index 8a6825ec..39bd1e2e 100644
--- a/docs/examples/with-code.qmd
+++ b/docs/examples/with-code.qmd
@@ -921,30 +921,6 @@ df = pd.DataFrame({
```
{{< include ./_show-last.qmd >}}
-
-### [gt_fmt_img_circle](https://posit-dev.github.io/gt-extras/reference/gt_fmt_img_circle.html){title="Click for reference"}
-```{python}
-# | label: gt_fmt_img_circle
-import polars as pl
-from great_tables import GT, vals, html
-import gt_extras as gte
-
-rich_avatar = "https://avatars.githubusercontent.com/u/5612024?v=4"
-michael_avatar = "https://avatars.githubusercontent.com/u/2574498?v=4"
-jules_avatar = "https://avatars.githubusercontent.com/u/54960783?v=4"
-
-
-df = pl.DataFrame({"@machow": [michael_avatar], "@rich-iannone": [rich_avatar], "@juleswg23": [jules_avatar]})
-
-(
- GT(df)
- .cols_align("center")
- .opt_stylize(color="green", style=6)
- .pipe(gte.gt_fmt_img_circle, height=80)
-)
-```
-{{< include ./_show-last.qmd >}}
-
### [gt_fa_rank_change](https://posit-dev.github.io/gt-extras/reference/gt_fa_rank_change.html){title="Click for reference"}
```{python}
# | label: gt_fa_rank_change
@@ -1002,6 +978,29 @@ gt.pipe(gte.gt_fa_rating, columns="rating", name="star")
```
{{< include ./_show-last.qmd >}}
+### [gt_fmt_img_circle](https://posit-dev.github.io/gt-extras/reference/gt_fmt_img_circle.html){title="Click for reference"}
+```{python}
+# | label: gt_fmt_img_circle
+import polars as pl
+from great_tables import GT
+import gt_extras as gte
+
+rich_avatar = "https://avatars.githubusercontent.com/u/5612024?v=4"
+michael_avatar = "https://avatars.githubusercontent.com/u/2574498?v=4"
+jules_avatar = "https://avatars.githubusercontent.com/u/54960783?v=4"
+
+
+df = pl.DataFrame({"@machow": [michael_avatar], "@rich-iannone": [rich_avatar], "@juleswg23": [jules_avatar]})
+
+(
+ GT(df)
+ .cols_align("center")
+ .opt_stylize(color="green", style=6)
+ .pipe(gte.gt_fmt_img_circle, height=80)
+)
+```
+{{< include ./_show-last.qmd >}}
+
### [img_header](https://posit-dev.github.io/gt-extras/reference/img_header.html){title="Click for reference"}
```{python}
# | label: img_header
diff --git a/gt_extras/images.py b/gt_extras/images.py
index f130b3ca..e43db45c 100644
--- a/gt_extras/images.py
+++ b/gt_extras/images.py
@@ -327,7 +327,7 @@ def gt_fmt_img_circle(
--------
```{python}
import polars as pl
- from great_tables import GT, vals, html
+ from great_tables import GT
import gt_extras as gte
rich_avatar = "https://avatars.githubusercontent.com/u/5612024?v=4"
From 69e6589fcc281190f5cfe93a97f94a27108277cf Mon Sep 17 00:00:00 2001
From: Jules <54960783+juleswg23@users.noreply.github.com>
Date: Mon, 18 Aug 2025 12:06:43 -0400
Subject: [PATCH 06/10] refactor
---
gt_extras/images.py | 54 ++++-----------------------------------------
1 file changed, 4 insertions(+), 50 deletions(-)
diff --git a/gt_extras/images.py b/gt_extras/images.py
index e43db45c..f2f2072d 100644
--- a/gt_extras/images.py
+++ b/gt_extras/images.py
@@ -4,6 +4,7 @@
from dataclasses import dataclass
from pathlib import Path
from typing import Any, ClassVar
+from warnings import warn
from great_tables import GT, html
from great_tables._gt_data import FormatFns
@@ -366,45 +367,11 @@ def gt_fmt_img_circle(
border_width, border_color, border_style = border_props.values()
- return _fmt_image(
- gt,
- columns=columns,
- rows=rows,
- height=height,
- width=width,
- border_radius=border_radius,
- border_width=border_width,
- border_color=border_color,
- border_style=border_style,
- sep=sep,
- path=path,
- file_pattern=file_pattern,
- encode=encode,
- )
-
-
-def _fmt_image(
- gt: GT,
- columns: SelectExpr = None,
- rows: int | list[int] | None = None,
- height: str | int | None = None,
- width: str | None = None,
- border_radius: str | None = None,
- border_width: str | int | None = None,
- border_color: str | None = None,
- border_style: str | None = None,
- sep: str = " ",
- path: str | Path | None = None,
- file_pattern: str = "{}",
- encode: bool = True,
-) -> GT:
- # TODO: most parameter options should allow a polars expression (or from_column) ----
- # can other fmt functions do this kind of thing?
expr_cols = [height, width, sep, path, file_pattern, encode]
if any(isinstance(x, PlExpr) for x in expr_cols):
raise NotImplementedError(
- "fmt_image currently does not support polars expressions for arguments other than"
+ "gt_fmt_img_circle currently does not support polars expressions for arguments other than"
" columns and rows"
)
@@ -470,20 +437,9 @@ def to_html(self, val: Any):
else:
height = self.height
- # TODO: note that only height can be numeric in the R program. Is this on purpose?
- # In any event, raising explicitly for numeric width below.
- if isinstance(self.width, (int, float)):
- raise NotImplementedError(
- "The `width=` argument must be specified as a string."
- )
- else:
- width = self.width
+ width = self.width
if self.border_radius is not None:
- if not isinstance(self.border_radius, str):
- raise NotImplementedError(
- "The `border_radius=` argument must be specified as a string."
- )
if not any(self.border_radius.endswith(suffix) for suffix in {"px", "%"}):
raise NotImplementedError(
'The `border_radius=` argument must end with either "px" or "%"'
@@ -538,9 +494,7 @@ def to_html(self, val: Any):
return span
def to_latex(self, val: Any):
- from warnings import warn
-
- from ._gt_data import FormatterSkipElement
+ from great_tables._gt_data import FormatterSkipElement
warn("fmt_image() is not currently implemented in LaTeX output.")
From 729bbcb14483b173e257aa52853a6ff81f03a3e6 Mon Sep 17 00:00:00 2001
From: Jules <54960783+juleswg23@users.noreply.github.com>
Date: Mon, 18 Aug 2025 12:07:00 -0400
Subject: [PATCH 07/10] tests!
---
.../tests/__snapshots__/test_images.ambr | 12 +
gt_extras/tests/test_images.py | 221 +++++++++++++++++-
2 files changed, 232 insertions(+), 1 deletion(-)
diff --git a/gt_extras/tests/__snapshots__/test_images.ambr b/gt_extras/tests/__snapshots__/test_images.ambr
index c70ee354..6bbe0913 100644
--- a/gt_extras/tests/__snapshots__/test_images.ambr
+++ b/gt_extras/tests/__snapshots__/test_images.ambr
@@ -12,6 +12,18 @@
'''
# ---
+# name: test_gt_fmt_img_circle_snapshot
+ '''
+
\n