Ag-Grid component with input support

I had just found the same problem and posted about it.
There is an error already when selecting or editing a cell, even without any other logic in the app.
I tried the demo and this error appearing almost in any grid.

@maajdl I got a response from @andfanilo in this thread on the issue - the streamlit team is already working on a fix :slight_smile:

Hi! For some reasons, while displaying data frame in the input grid (editing mode), some of the columns appear empty and thus cannot be edited. To be precise, 2 columns with float values and 1 with integers are always empty, although in the original data frame they are not

This is especially strange, because other columns with floats, integers and dates in the same data frame are completely fine (they are displayed/can be edited). The code is simple:

grid_return = AgGrid(df, editable=True, reload_data=False)

What could be the source of the problem? Thanks!

@PablocFonseca Thank you so much for this fantastic invention. Just wondering is there any limitation in number of rows displaying. Im getting “RuntimeError: Data of size 50.7MB exceeds write limit of 50.0MB” error even though its around 6 MB. But it has 11000 rows. Any thoughts on this?

Hi @dxz6228 Any luck with getting selected rows? Im also having same sure. Not sure what is the solution for it. :frowning:

@rchamila @dxz6228 Don’t know if it’s still relevant to you, but I am able to get rows selection work via the below snippet

def display_table(df: pd.DataFrame) -> AgGrid:
    # Configure AgGrid options
    gb = GridOptionsBuilder.from_dataframe(df)
    gb.configure_selection('single')
    return AgGrid(
        df,
        gridOptions=gb.build(),
        # this override the default VALUE_CHANGED
        update_mode=GridUpdateMode.MODEL_CHANGED
        )

Hope this helps! :muscle:

2 Likes

Thansk @sis , Yeah it was helpful and managed to fix the issue with row selected. But I just want to delete these selected rows and display the updated table. Any thoughts on how to do that?

@rchamila a possible solution to your case is given by the below snippet (consider that I am no expert in neither AG Grid nor streamlit-aggrid components, so maybe some implementations can be improved)

It works for both single and multiple rows selection.

Snippet
import string

import numpy as np
import pandas as pd
import streamlit as st
from st_aggrid import AgGrid, GridOptionsBuilder, GridUpdateMode

st.set_page_config(layout='wide')


def display_table(df: pd.DataFrame, selection: str) -> AgGrid:
    # Configure AgGrid options
    gb = GridOptionsBuilder.from_dataframe(df)
    gb.configure_selection(selection)
    st.write(f"Dataframe shape: {df.shape}")
    return AgGrid(
        df,
        gridOptions=gb.build(),
        # this override the default VALUE_CHANGED
        update_mode=GridUpdateMode.MODEL_CHANGED
    )


# Define dummy data
rng = np.random.default_rng(2021)
N_SAMPLES = 100
N_FEATURES = 10
df = pd.DataFrame(rng.integers(0, N_SAMPLES, size=(
    N_SAMPLES, N_FEATURES)), columns=list(string.ascii_uppercase[:N_FEATURES]))

cols = st.beta_columns(2)

with cols[0]:

    st.markdown('# 🠔 Before')

    # Display AgGrid from data and write response
    st.markdown("### 1️⃣ Let's display dummy data through AgGrid")
    selection = st.radio('Selection mode', ['single', 'multiple'])
    response = display_table(df, selection=selection)

    st.markdown(
        "### 2️⃣ AgGrid response contains `data` (original df) and `selected_rows`")
    for k, v in response.items():
        st.write(k, v)

with cols[1]:

    st.markdown('# 🠖 After')

    # Retrieve selected rows indices
    st.markdown(
        "### 3️⃣ From selected rows we can obtain dataframe indices to drop")
    data = response['data'].to_dict(orient='records')
    indices = [data.index(row) for row in response['selected_rows']]
    st.write(f"Selected rows are located at indices: {indices}")

    # Use retrieved indices to remove corresponding rows from dataframe
    st.markdown(
        "### 4️⃣ Display the updated dataframe where rows have been removed")
    _df = df.drop(indices, axis=0)
    st.write(f"Dataframe shape: {_df.shape}")
    AgGrid(_df)
3 Likes

Thanks @sis, Will that update the same AgGrid ? or will that shows a new one? I want to update the same AgGrid.

@rchamila the provided snippet will create and display a new pandas dataframe based on:

  • the original one
  • rows selected for removal

If you need to update the original dataframe, it should be sufficient to change last three codelines by overwriting df rather than create _df

@sis , Thanks for trying to sort this out. What I need is to update the same grid in the UI. Rather than having a separate upgraded grid.

With your solution, seems it shows a new grid as shown below screenshot

@rchamila following this example in streamlit-aggrid repo and this blog post, I found a way to achieve what you need (interactive row deletion) through custom JavaScript injection

Solution
import string

import numpy as np
import pandas as pd
import streamlit as st
from st_aggrid import AgGrid, GridOptionsBuilder, GridUpdateMode, JsCode

st.set_page_config(layout='wide')


def display_table(df: pd.DataFrame) -> AgGrid:
    # Configure AgGrid options
    gb = GridOptionsBuilder.from_dataframe(df)
    gb.configure_selection('single', use_checkbox=True)
    
    # Custom JS code for interactive rows deletion
    # For credits SEE: 
    # https://github.com/PablocFonseca/streamlit-aggrid/blob/1acb526ba43b5aac9c8eb22cc54eeb05696cd84d/examples/example_highlight_change.py#L21
    # https://ag-grid.zendesk.com/hc/en-us/articles/360020160932-Removing-selected-rows-or-cells-when-Backspace-or-Delete-is-pressed
    js = JsCode("""
    function(e) {
        let api = e.api;        
        let sel = api.getSelectedRows();
        
        api.applyTransaction({remove: sel});
    };
    """)
    gb.configure_grid_options(onRowSelected=js) 
    return AgGrid(
        df,
        gridOptions=gb.build(),
        # this override the default VALUE_CHANGED
        update_mode=GridUpdateMode.MODEL_CHANGED,
        # needed for js injection
        allow_unsafe_jscode=True
    )


# Define dummy data
rng = np.random.default_rng(2021)
N_SAMPLES = 100
N_FEATURES = 10
df = pd.DataFrame(rng.integers(0, N_SAMPLES, size=(
    N_SAMPLES, N_FEATURES)), columns=list(string.ascii_uppercase[:N_FEATURES]))

st.info("Select a row to remove it")
response = display_table(df)
st.write(f"Dataframe shape: {response['data'].shape}")
1 Like

Genius, That sounds promising. Any idea what would be the event of Update button click? . Then I can set it to the grid options instead of onRowSelected event.

Thanks
Chamila

Nope, unfortunately as I said I’m just discovering AgGrid. Maybe component creator @PablocFonseca can help you.

Finally I had some spare time, and updated to version 0.2.0, which supports theming. Checkout the examples folder

4 Likes

Hi, I’m wondering if it is possible to color whole rows based on values in a single column. Does anyone know?

4 Likes

Hi @PablocFonseca, thanks for all your work, it’s super useful!
I’m struggling with columns formatting. I’m not able to format columns as percentages, and not able to have a thousand separators for numbers. Do you have a solution for this?
Thanks a lot!

Hi @Pierre!

To get comma separators (or full stop separators depending on locale (i.e. your browser’s language settings)), using the gridbuilder package as in the examples, for the column you’re talking about try something like:

gb.configure_column("numeric_column_a", header_name='Numeric Column A', type=["numericColumn","numberColumnFilter"], valueFormatter="data.numeric_column_a.toLocaleString(undefined, {minimumFractionDigits: 0, maximumFractionDigits: 0})", aggFunc='sum')

I believe it should work in most browsers. As for percentages, I often use percentages in column headings for ease, but it can help readability so it’s understandable to want them within the table! If your number is between 0 and 1, to show as a percentage use something like:

gb.configure_column("percentage_column_b", header_name='Percentage Column B', type=["numericColumn","numberColumnFilter"], valueFormatter="(data.percentage_column_b*100).toFixed(1)+'%', aggFunc='sum')

Hope this helps!

Hi @PablocFonseca

Again, awesome work on this component.

I have a question: Is it currently possible to change the text in the “Update” button that it shown when update_mode is set to “MANUAL”?

Best,
Peter

Hello!

No, it’s not…

but this feature became obsolete with the introduction of streamlit forms. You can wrap the grid in a form and then place a submit button instead.

1 Like