great_tables
  • Get Started
  • Examples
  • Reference
  • Blog

On this page

  • Rendering Images in the Body
    • Preparations
    • Case 1: Local File Paths
    • Case 2: Full HTTP/HTTPS URLs
    • Case 3: Image Names with the path= Argument
    • Case 4: Image Names Using Both the path= and file_pattern= Arguments
  • Rendering Images Anywhere
    • Preparations
    • Single Image
    • Multiple Images
  • Manually Rendering Images Anywhere
  • Final Words

Rendering images anywhere in Great Tables

Author

Jerry Wu

Published

December 13, 2024

Rendering images in Great Tables is straightforward with GT.fmt_image() and vals.fmt_image(). In this post, we’ll explore three key topics:

  • Four examples demonstrating how to render images within the body using GT.fmt_image().
  • How to render images anywhere using vals.fmt_image() and html().
  • How to manually render images anywhere using html().

Rendering Images in the Body

GT.fmt_image() is the go-to tool for rendering images within the body of a table. Below, we’ll present four examples corresponding to the cases outlined in the documentation:

  • Case 1: Local file paths.
  • Case 2: Full HTTP/HTTPS URLs.
  • Case 3: Image names with the path= argument.
  • Case 4: Image names using both the path= and file_pattern= arguments.
Finding the Right Case for Your Needs
  • Case 1 and Case 2 work best for data sourced directly from a database.
  • Case 3 is ideal for users dealing with image names relative to a base directory or URL (e.g., /path/to/images).
  • Case 4 is tailored for users working with patterned image names (e.g., metro_{}.svg).

Preparations

For this demonstration, we’ll use the first five rows of the built-in metro dataset, specifically the name and lines columns.

To ensure a smooth walkthrough, we’ll manipulate the data (a Python dictionary) directly. However, in real-world applications, such operations are more likely performed at the DataFrame level to leverage the benefits of vectorized operations.

Show the Code
import pandas as pd
from great_tables import GT, vals, html
from importlib_resources import files

pd.set_option('display.max_colwidth', 150)

data = {
    "name": [
        "Argentine",
        "Bastille",
        "Bérault",
        "Champs-Élysées—Clemenceau",
        "Charles de Gaulle—Étoile",
    ],
    "lines": ["1", "1, 5, 8", "1", "1, 13", "1, 2, 6"],
}

print("""\
data = {
    "name": [
        "Argentine",
        "Bastille",
        "Bérault",
        "Champs-Élysées—Clemenceau",
        "Charles de Gaulle—Étoile",
    ],
    "lines": ["1", "1, 5, 8", "1", "1, 13", "1, 2, 6"],
}\
""")
data = {
    "name": [
        "Argentine",
        "Bastille",
        "Bérault",
        "Champs-Élysées—Clemenceau",
        "Charles de Gaulle—Étoile",
    ],
    "lines": ["1", "1, 5, 8", "1", "1, 13", "1, 2, 6"],
}

Attentive readers may have noticed that the values for the key lines are lists of strings, each containing one or more numbers separated by commas. GT.fmt_image() is specifically designed to handle such cases, allowing users to render multiple images in a single row.

Case 1: Local File Paths

Case 1 demonstrates how to simulate a column containing strings representing local file paths. We’ll use images stored in the data/metro_images directory of Great Tables:

1img_local_paths = files("great_tables") / "data/metro_images"
1
These image files follow a patterned naming convention, such as metro_1.svg, metro_2.svg, and so on.

Below is a Pandas DataFrame called metro_mini1, where the case1 column contains local file paths that we want to render as images.

Show the Code
metro_mini1 = pd.DataFrame(
    {
        **data,
        "case1": [
            ", ".join(
                str((img_local_paths / f"metro_{item}").with_suffix(".svg"))
                for item in row.split(", ")
            )
            for row in data["lines"]
        ],
    }
)
metro_mini1
name lines case1
0 Argentine 1 /opt/hostedtoolcache/Python/3.10.17/x64/lib/python3.10/site-packages/great_tables/data/metro_images/metro_1.svg
1 Bastille 1, 5, 8 /opt/hostedtoolcache/Python/3.10.17/x64/lib/python3.10/site-packages/great_tables/data/metro_images/metro_1.svg, /opt/hostedtoolcache/Python/3.10....
2 Bérault 1 /opt/hostedtoolcache/Python/3.10.17/x64/lib/python3.10/site-packages/great_tables/data/metro_images/metro_1.svg
3 Champs-Élysées—Clemenceau 1, 13 /opt/hostedtoolcache/Python/3.10.17/x64/lib/python3.10/site-packages/great_tables/data/metro_images/metro_1.svg, /opt/hostedtoolcache/Python/3.10....
4 Charles de Gaulle—Étoile 1, 2, 6 /opt/hostedtoolcache/Python/3.10.17/x64/lib/python3.10/site-packages/great_tables/data/metro_images/metro_1.svg, /opt/hostedtoolcache/Python/3.10....
Use the pathlib Module to Construct Paths

Local file paths can vary depending on the operating system, which makes it easy to accidentally construct invalid paths. A good practice to mitigate this is to use Python’s built-in pathlib module to construct paths first and then convert them to strings. In this example, img_local_paths is actually an instance of pathlib.Path.

from pathlib import Path

isinstance(img_local_paths, Path)  # True

The case1 column is quite lengthy due to the inclusion of img_local_paths. In Case 3, we’ll share a useful trick to avoid repeating the directory name each time—stay tuned!

For now, let’s use GT.fmt_image() to render images by passing "case1" as the first argument:

GT(metro_mini1).fmt_image("case1").cols_align(align="right", columns="case1")
name lines case1
Argentine 1
Bastille 1, 5, 8
Bérault 1
Champs-Élysées—Clemenceau 1, 13
Charles de Gaulle—Étoile 1, 2, 6

Case 2: Full HTTP/HTTPS URLs

Case 2 demonstrates how to simulate a column containing strings representing HTTP/HTTPS URLs. We’ll use the same images as in Case 1, but this time, retrieve them from the Great Tables GitHub repository:

img_url_paths = "https://raw.githubusercontent.com/posit-dev/great-tables/refs/heads/main/great_tables/data/metro_images"

Below is a Pandas DataFrame called metro_mini2, where the case2 column contains full HTTP/HTTPS URLs that we aim to render as images.

Show the Code
metro_mini2 = pd.DataFrame(
    {
        **data,
        "case2": [
            ", ".join(f"{img_url_paths}/metro_{item}.svg" for item in row.split(", "))
            for row in data["lines"]
        ],
    }
)
metro_mini2
name lines case2
0 Argentine 1 https://raw.githubusercontent.com/posit-dev/great-tables/refs/heads/main/great_tables/data/metro_images/metro_1.svg
1 Bastille 1, 5, 8 https://raw.githubusercontent.com/posit-dev/great-tables/refs/heads/main/great_tables/data/metro_images/metro_1.svg, https://raw.githubusercontent...
2 Bérault 1 https://raw.githubusercontent.com/posit-dev/great-tables/refs/heads/main/great_tables/data/metro_images/metro_1.svg
3 Champs-Élysées—Clemenceau 1, 13 https://raw.githubusercontent.com/posit-dev/great-tables/refs/heads/main/great_tables/data/metro_images/metro_1.svg, https://raw.githubusercontent...
4 Charles de Gaulle—Étoile 1, 2, 6 https://raw.githubusercontent.com/posit-dev/great-tables/refs/heads/main/great_tables/data/metro_images/metro_1.svg, https://raw.githubusercontent...

The lengthy case2 column issue can also be addressed using the trick shared in Case 3.

Similarly, we can use GT.fmt_image() to render images by passing "case2" as the first argument:

GT(metro_mini2).fmt_image("case2").cols_align(align="right", columns="case2")
name lines case2
Argentine 1
Bastille 1, 5, 8
Bérault 1
Champs-Élysées—Clemenceau 1, 13
Charles de Gaulle—Étoile 1, 2, 6

Case 3: Image Names with the path= Argument

Case 3 demonstrates how to use the path= argument to specify images relative to a base directory or URL. This approach eliminates much of the repetition in file names, offering a solution to the issues in Case 1 and Case 2.

Below is a Pandas DataFrame called metro_mini3, where the case3 column contains file names that we aim to render as images.

Show the Code
metro_mini3 = pd.DataFrame(
    {
        **data,
        "case3": [
            ", ".join(f"metro_{item}.svg" for item in row.split(", ")) for row in data["lines"]
        ],
    }
)
metro_mini3
name lines case3
0 Argentine 1 metro_1.svg
1 Bastille 1, 5, 8 metro_1.svg, metro_5.svg, metro_8.svg
2 Bérault 1 metro_1.svg
3 Champs-Élysées—Clemenceau 1, 13 metro_1.svg, metro_13.svg
4 Charles de Gaulle—Étoile 1, 2, 6 metro_1.svg, metro_2.svg, metro_6.svg

Now we can use GT.fmt_image() to render the images by passing "case3" as the first argument and specifying either img_local_paths or img_url_paths as the path= argument:

# equivalent to `Case 1`
(
    GT(metro_mini3)
    .fmt_image("case3", path=img_local_paths)
    .cols_align(align="right", columns="case3")
)

# equivalent to `Case 2`
(
    GT(metro_mini3)
    .fmt_image("case3", path=img_url_paths)
    .cols_align(align="right", columns="case3")
)
name lines case3
Argentine 1
Bastille 1, 5, 8
Bérault 1
Champs-Élysées—Clemenceau 1, 13
Charles de Gaulle—Étoile 1, 2, 6

After exploring Case 1 and Case 2, you’ll likely appreciate the functionality of the path= argument. However, manually constructing file names can still be a bit tedious. If your file names follow a consistent pattern, the file_pattern= argument can simplify the process. Let’s see how this works in Case 4 below.

Case 4: Image Names Using Both the path= and file_pattern= Arguments

Case 4 demonstrates how to use path= and file_pattern= to specify images with names following a common pattern. For example, you could use file_pattern="metro_{}.svg" to reference images like metro_1.svg, metro_2.svg, and so on.

Below is a Pandas DataFrame called metro_mini4, where the case4 column contains a copy of data["lines"], which we aim to render as images.

Show the Code
metro_mini4 = pd.DataFrame({**data, "case4": data["lines"]})
metro_mini4
name lines case4
0 Argentine 1 1
1 Bastille 1, 5, 8 1, 5, 8
2 Bérault 1 1
3 Champs-Élysées—Clemenceau 1, 13 1, 13
4 Charles de Gaulle—Étoile 1, 2, 6 1, 2, 6

First, define a string pattern to illustrate the file naming convention, using {} to indicate the variable portion:

file_pattern = "metro_{}.svg"

Next, pass "case4" as the first argument, along with img_local_paths or img_url_paths as the path= argument, and file_pattern as the file_pattern= argument. This allows GT.fmt_image() to render the images:

# equivalent to `Case 1`
(
    GT(metro_mini4)
    .fmt_image("case4", path=img_local_paths, file_pattern=file_pattern)
    .cols_align(align="right", columns="case4")
)

# equivalent to `Case 2`
(
    GT(metro_mini4)
    .fmt_image("case4", path=img_url_paths, file_pattern=file_pattern)
    .cols_align(align="right", columns="case4")
)
name lines case4
Argentine 1
Bastille 1, 5, 8
Bérault 1
Champs-Élysées—Clemenceau 1, 13
Charles de Gaulle—Étoile 1, 2, 6
Using file_pattern= Independently

The file_pattern= argument is typically used in conjunction with the path= argument, but this is not a strict rule. If your local file paths or HTTP/HTTPS URLs follow a pattern, you can use file_pattern= alone without path=. This allows you to include the shared portion of the file paths or URLs directly in file_pattern, as shown below:

file_pattern = str(img_local_paths / "metro_{}.svg")
(
    GT(metro_mini4)
    .fmt_image("case4", file_pattern=file_pattern)
    .cols_align(align="right", columns="case4")
)
name lines case4
Argentine 1
Bastille 1, 5, 8
Bérault 1
Champs-Élysées—Clemenceau 1, 13
Charles de Gaulle—Étoile 1, 2, 6

Case 4 is undoubtedly one of the most powerful features of Great Tables. While mastering it may take some practice, we hope this example helps you render images effortlessly and effectively.

Rendering Images Anywhere

While GT.fmt_image() is primarily designed for rendering images in the table body, what if you need to display images in other locations, such as the header? In such cases, you can turn to the versatile vals.fmt_image().

vals.fmt_image() is a hidden gem in Great Tables. Its usage is similar to GT.fmt_image(), but instead of working directly with DataFrame columns, it lets you pass a string or a list of strings as the first argument, returning a list of strings, each representing an image. You can then wrap these strings with html(), allowing Great Tables to render the images anywhere in the table.

Preparations

We will create a Pandas DataFrame named metro_mini using the data dictionary. This will be used for demonstration in the following examples:

Show the Code
metro_mini = pd.DataFrame(data)
metro_mini
name lines
0 Argentine 1
1 Bastille 1, 5, 8
2 Bérault 1
3 Champs-Élysées—Clemenceau 1, 13
4 Charles de Gaulle—Étoile 1, 2, 6

Single Image

This example shows how to render a valid URL as an image in the title of the table header:

gt_logo_url = "https://posit-dev.github.io/great-tables/assets/GT_logo.svg"

1_gt_logo, *_ = vals.fmt_image(gt_logo_url, height=100)
gt_logo = html(_gt_logo)

(
    GT(metro_mini)
    .fmt_image("lines", path=img_url_paths, file_pattern="metro_{}.svg")
    .tab_header(title=gt_logo)
    .cols_align(align="right", columns="lines")
    .opt_stylize(style=4, color="gray")
)
1
vals.fmt_image() returns a list of strings. Here, we use tuple unpacking to extract the first item from the list.
name lines
Argentine
Bastille
Bérault
Champs-Élysées—Clemenceau
Charles de Gaulle—Étoile

Multiple Images

This example demonstrates how to render two valid URLs as images in the title and subtitle of the table header:

metro_logo_url = "https://raw.githubusercontent.com/rstudio/gt/master/images/dataset_metro.svg"
logo_urls = [gt_logo_url, metro_logo_url]

1_gt_logo, _metro_logo = vals.fmt_image(logo_urls, height=100)
gt_logo, metro_logo = html(_gt_logo), html(_metro_logo)

(
    GT(metro_mini)
    .fmt_image("lines", path=img_url_paths, file_pattern="metro_{}.svg")
    .tab_header(title=gt_logo, subtitle=metro_logo)
    .cols_align(align="right", columns="lines")
    .opt_stylize(style=4, color="gray")
)
1
Note that if you need to render images with different height or width, you might need to make two separate calls to vals.fmt_image().
name lines
Argentine
Bastille
Bérault
Champs-Élysées—Clemenceau
Charles de Gaulle—Étoile

Manually Rendering Images Anywhere

Remember, you can always use html() to manually construct your desired output. For example, the previous table can be created without relying on vals.fmt_image() like this:

(
    GT(metro_mini)
    .fmt_image("lines", path=img_url_paths, file_pattern="metro_{}.svg")
    .tab_header(
        title=html(f'<img src="{gt_logo_url}" height="100">'),
        subtitle=html(f'<img src="{metro_logo_url}" height="100">'),
    )
    .cols_align(align="right", columns="lines")
    .opt_stylize(style=4, color="gray")
)

Alternatively, you can manually encode the image using Python’s built-in base64 module, specify the appropriate MIME type and HTML attributes, and then wrap it in html() to display the table.

Final Words

In this post, we focused on the most common use cases for rendering images in Great Tables, deliberately avoiding excessive DataFrame operations. Including such details could have overwhelmed the post with examples of string manipulations and the complexities of working with various DataFrame libraries.

We hope you found this guide helpful and enjoyed the structured approach. Until next time, happy table creation with Great Tables!

Appendix: Related PRs

If you’re interested in the recent enhancements we’ve made to image rendering, be sure to check out #444, #451 and #520 for all the details.