Display images in AgGrid table

Hi all,
this is my first post and I have to say I already love Streamlit :slight_smile:

My question is: can I display an image in a table created with the component AgGrid?

Thanks,
Vins

1 Like

I came looking for this feature. Currently displaying a peek of file paths, would be handy to display the related images as a thumbnail

Hi @Tobias_Schiele , @Vincenzo_Cacciatore , @Shinigami86 has already managed to do this. Check out this link: Ag-Grid component with input support - #63 by PablocFonseca

Cheers

Wow, thank you so much for your fast reply. That’s definitely something to work with. At the moment I am not able to use a JS solution as my image assets can exist outside of the streamlit host directory, thus they have to be loaded on the server-side. But hey, it’s a start! Maybe I can load each image, create a thumbnail on the fly and directly communicate the images to AgGrid. Thank you!

Hi,

I tried to adapt the code to display images in Ag-grid Table cell but It’s not working.

import pandas as pd
from st_aggrid import AgGrid, GridUpdateMode, DataReturnMode, JsCode
from st_aggrid.grid_options_builder import GridOptionsBuilder


df = pd.DataFrame({"Name": ["Elon", "Bill"],
                    "Image":[{'src':"https://fr.wikipedia.org/wiki/Elon_Musk#/media/Fichier:Elon_Musk_Royal_Society.jpg", 'alt':'Elon'},
                            {'src':"https://fr.wikipedia.org/wiki/Bill_Gates#/media/Fichier:Bill_Gates_-_Nov._8,_2019.jpg", 'alt':'Bill'},
                            ],
                    })

options_builder = GridOptionsBuilder.from_dataframe(df)

image_render = JsCode("""function (params) {
            this.params = params;
            this.img_render = document.createElement('div');
            
            this.img_render.innerHTML = `
                <img src=${this.params.src}
                    alt=${this.params.alt}
                    width="50"
                    height="50"
                >
            `;
            
            return this.img_render;
            }""")

options_builder.configure_column("Image",
        headerName="Image",
        cellRenderer=image_render)

grid_options = options_builder.build()

grid_return = AgGrid(df,
                    grid_options,
                    theme="streamlit",
                    allow_unsafe_jscode=True,
                    ) 

Any suggestions?

Hi @Marc_Richard, this has already been done. Check out this link: Ag-Grid component with input support - #63 by PablocFonseca

Cheers

Hi Shawn,
yes, I already tried this explore this example but I didn’t find how to code the right dataframe since in the post, he only shared the JScode and not the rest of the code. I am not sure why this is params.data.NATION_PATH and params.value because there is no example of the data column construction.
Any ideas?
thank you.

UPDATE:
my image url was not correct! It works now by using the example of PablocFonseca and the right image url.

import pandas as pd
from st_aggrid import AgGrid, GridUpdateMode, DataReturnMode, JsCode
from st_aggrid.grid_options_builder import GridOptionsBuilder


df = pd.DataFrame({"image_path":["https://upload.wikimedia.org/wikipedia/commons/thumb/e/ed/Elon_Musk_Royal_Society.jpg/800px-Elon_Musk_Royal_Society.jpg",
                                    "https://upload.wikimedia.org/wikipedia/commons/thumb/a/af/Bill_Gates_-_Nov._8%2C_2019.jpg/390px-Bill_Gates_-_Nov._8%2C_2019.jpg"],
                    "Name":['Elon', 'Bill']
                    })

options_builder = GridOptionsBuilder.from_dataframe(df)


image_nation = JsCode("""function (params) {
        console.log(params);
        var element = document.createElement("span");
        var imageElement = document.createElement("img");
    
        imageElement.src = params.data.image_path;
        imageElement.width="40";
        imageElement.height="40";

        element.appendChild(imageElement);
        element.appendChild(document.createTextNode(params.value));
        return element;
        }""")
options_builder.configure_column('Name', cellRenderer=image_nation)


grid_options = options_builder.build()

grid_return = AgGrid(df,
                    grid_options,
                    theme="streamlit",
                    allow_unsafe_jscode=True,
                    ) 

Hi @Marc_Richard

Code Explanation: I have created a dataframe with 4 columns - this is fairly self-explanatory.
The 1st 2 images are stored locally on my hard drive in the mentioned path (which you can change if you plan to use local images). These need to be base64 and the function ‘ReadPictureFile’ does that conversion. For all local images, I iterrows of the dataframe and add the preliminary text ‘data:image/…’ - ref code

The last 2 images are directly accessed from the web and does not need any change in the image path column.

I hide the image path column, as this is not needed to be displayed.

I show the picture in the Face column.

Before trying the following code, you need to copy 2 images to your local folder and change the path and filenames of the 1st 2 images in the code. The code is below:

import streamlit as st
import pandas as pd
from st_aggrid import GridOptionsBuilder, AgGrid
from st_aggrid.shared import JsCode
import base64

def ReadPictureFile(wch_fl):
    try:
        return base64.b64encode(open(wch_fl, 'rb').read()).decode()

    except:
        return ""

ShowImage = JsCode("""function (params) {
            var element = document.createElement("span");
            var imageElement = document.createElement("img");
        
            if (params.data.ImgPath != '') {
                imageElement.src = params.data.ImgPath;
                imageElement.width="20";
            } else { imageElement.src = ""; }
            element.appendChild(imageElement);
            element.appendChild(document.createTextNode(params.value));
            return element;
            }""")


df = pd.DataFrame({'Name': ['Iron Man', 'Walter White', 'Wonder Woman', 'Bat Woman'],
                   'Face': ['', '', '', ''],
                   'ImgLocation': ['Local', 'Local', 'Web', 'Web'],
                   'ImgPath': ['D:/ShawnP/icon-im.png', 
                               'D:/ShawnP/icon-ww.png', 
                               'https://i.pinimg.com/originals/ab/26/c3/ab26c351e435242658c3783710e78163.jpg',
                               'https://img00.deviantart.net/85f5/i/2012/039/a/3/batwoman_headshot_4_by_ricber-d4p1y86.jpg'] })

if df.shape[0] > 0:
    for i, row in df.iterrows():
        if row.ImgLocation == 'Local':
            imgExtn = row.ImgPath[-4:]
            row.ImgPath = f'data:image/{imgExtn};base64,' + ReadPictureFile(row.ImgPath)

gb = GridOptionsBuilder.from_dataframe(df)
gb.configure_column('Face', cellRenderer=ShowImage)
gb.configure_column("ImgPath", hide = "True")

vgo = gb.build()
AgGrid(df, gridOptions=vgo, theme='blue', height=150, allow_unsafe_jscode=True )

Hope the above is understandable.

Cheers

aggimg

2 Likes

This solution no longer seems to work, nor does the one described here: Enhancing AgGrid table with Image Display in Streamlit Apps | by Avra | The Streamlit Teacher | Medium.

I’m getting an error in the streamlit app page " Component Error: Minified React error #31; visit https://reactjs.org/docs/error-decoder.html?invariant=31&args[]=[object%20HTMLImageElement] for the full message or use the non-minified dev environment for full errors and additional helpful warnings."

Does anybody know how to resolve this?

2 Likes

I figured it out myself. Here is what I did:

Custom cell renderer for Thumbnail column

thumbnail_renderer = JsCode(“”"
class ThumbnailRenderer {
init(params) {
this.eGui = document.createElement(‘img’);
this.eGui.setAttribute(‘src’, params.value);
this.eGui.setAttribute(‘width’, ‘100’);
this.eGui.setAttribute(‘height’, ‘auto’);
}
getGui() {
return this.eGui;
}
}
“”")

gb.configure_column(“Thumbnail”, “Thumbnail”, cellRenderer=thumbnail_renderer)

3 Likes

Can you provide more info? I’m trying to work with it but got undefined

this worked for me as well, thank you for the solution @o7gyk9wlr :

  thumbnail_renderer = JsCode("""
        class ThumbnailRenderer {
            init(params) {

            this.eGui = document.createElement('img');
            this.eGui.setAttribute('src', params.value);
            this.eGui.setAttribute('width', '100');
            this.eGui.setAttribute('height', 'auto');
            }
                getGui() {
                console.log(this.eGui);

                return this.eGui;
            }
        }
    """)

    gd.configure_column("IMAGE_URL", cellRenderer=thumbnail_renderer)

for some reason this did not >

image_renderer = JsCode(
        """
        function renderImage(params) {
            var img = document.createElement('img');
            img.src = params.value;
            img.alt = 'Your Image';
            img.width = 50;
            img.height = 50;
            console.log(img);
            return img;
        }
        
        
        """
    )
    gd.configure_column('IMAGE_URL', cellRenderer=image_renderer)

I would get the error: Minified React error #31

Minified React error #31 – React.

I asked chat gpt and the answer was:

but why in the new version I am not getting the minified react error 31 Objects are not valid as a React child (found: [object HTMLImageElement]). If you meant to render a collection of children, use an array instead.

ChatGPT:

The reason you’re not getting the minified React error #31 in your version is because you’re not directly returning the HTML element itself from your JavaScript code. Instead, you’re encapsulating the HTML element within a JavaScript class (ThumbnailRenderer) and returning an instance of that class.

When Ag-Grid renders the cell content, it expects a valid React component or an array of React components. In your class-based implementation, you’re effectively returning a custom class instance (ThumbnailRenderer) which Ag-Grid handles as a valid React component, allowing it to render without triggering the error.

In contrast, in the previous implementations where you directly returned the HTML element (img), React attempted to render the HTML element itself, which is not a valid React component and resulted in the error. By encapsulating the HTML element within a custom class and returning an instance of that class, you’re effectively providing a valid React component for Ag-Grid to render, thus avoiding the error.

I expanded the code since I want to pop over the image inside the page when the user clicks it, just like in the st.data_editor, and used ChatGPT again, the code:


class ThumbnailRenderer {
    init(params) {
        this.eGui = document.createElement('div'); // Create a container div
        this.eGui.style.display = 'flex'; // Set display to flex
        this.eGui.style.justifyContent = 'center'; // Center horizontally
        this.eGui.style.alignItems = 'center'; // Center vertically
        const img = document.createElement('img'); // Create img element
        img.setAttribute('src', params.value); // Set src attribute
        img.setAttribute('width', '100'); // Set width
        img.setAttribute('height', 'auto'); // Set height to auto to maintain aspect ratio
        img.style.cursor = 'pointer'; // Add pointer cursor to indicate clickable
        this.eGui.appendChild(img); // Append img to container div

        // Add event listener for click event on the image
        img.addEventListener('click', () => {
            // Create a modal or popover to display the enlarged image
            const modal = document.createElement('div');
            modal.style.position = 'fixed';
            modal.style.top = '0';
            modal.style.left = '0';
            modal.style.width = '100%';
            modal.style.height = '100%';
            modal.style.backgroundColor = 'rgba(0, 0, 0, 0.5)';
            modal.style.display = 'flex';
            modal.style.justifyContent = 'center';
            modal.style.alignItems = 'center';

            const enlargedImg = document.createElement('img');
            enlargedImg.setAttribute('src', params.value);
            enlargedImg.style.maxWidth = '90%';
            enlargedImg.style.maxHeight = '90%';

            modal.appendChild(enlargedImg);

            // Close modal when clicking outside the image
            modal.addEventListener('click', (event) => {
                if (event.target === modal) {
                    modal.remove();
                }
            });

            document.body.appendChild(modal);
        });
    }

    getGui() {
        return this.eGui;
    }
}

The result