While Streamlit is a powerful framework for building data applications, displaying complex tabular data has some limitations with its native components. Streamlit-AgGrid bridges this gap by providing the feature-rich AgGrid JavaScript library with a Pythonic interface that’s both intuitive and powerful. This combination allows you to create sophisticated, interactive data tables with ease, enhancing your Streamlit applications with capabilities such as:

  • Advanced filtering across multiple columns
  • Sophisticated sorting mechanisms
  • Inline cell editing
  • Responsive design and performance optimization
  • Row grouping and aggregation
  • Custom cell rendering
  • Event handling and interactivity

This guide will walk you through everything you need to know about using AgGrid in your Streamlit applications. For each example, you’ll be able to see a live Streamlit demo by simply clicking the Play button. These demos are provided by Editor.ploomber.io, an AI-powered Streamlit editor that’s perfect for generating data-first apps and easily sharing them.

Getting Started

Installation

First, install the required packages:

pip install streamlit streamlit-aggrid pandas

Basic Usage

Let’s start with a simple example to display a DataFrame:

import streamlit as st
import pandas as pd
from st_aggrid import AgGrid

# Load sample dataset
df = pd.read_json("https://www.ag-grid.com/example-assets/olympic-winners.json")

# Display the grid
grid_return = AgGrid(df)

This basic implementation already provides a powerful table with:

  • Column reordering
  • Column resizing
  • Default sorting capabilities
  • Responsive layout

Core Concepts

Streamlit AgGrid consists of three major components:

AgGrid function

  • This is used to display the table, as we’ve seen in previous examples.

GridOptionsBuilder

  • This takes a DataFrame as input and returns a Python object. With this object, you can configure multiple options sequentially, ultimately building the final gridOptions that will be passed to the AgGrid function which is responsible for the customization.
import streamlit as st
import pandas as pd
from st_aggrid import AgGrid, GridOptionsBuilder

# Load your dataset
df = pd.read_json("https://www.ag-grid.com/example-assets/olympic-winners.json")

# Configure grid options
gb = GridOptionsBuilder.from_dataframe(df)
gb.configure_default_column(
    groupable=True,
    value=True,
    enableRowGroup=True,
    aggFunc='sum'
)

# Customize specific column behaviors
gb.configure_column('country', header_name="Home Country")
gb.configure_pagination(enabled=True, paginationPageSize=5)
gb.configure_selection(selection_mode="multiple", use_checkbox=True)
gb.configure_side_bar(filters_panel=True, defaultToolPanel='filters')

# Build the final option object
grid_options = gb.build()

# Render AgGrid
AgGrid(
    df,
    gridOptions=grid_options,
    height=400,
    width='100%',
    allow_unsafe_jscode=True
)

st.markdown("### All Possible builder options")
for p in dir(GridOptionsBuilder):
    if not p.startswith("_"):
        _ = gb.__getattribute__(p)
        st.write(_)

AgGridReturn Object

  • This object is a crucial component returned by the AgGrid function. It encapsulates the information about the grid’s current state and user interactions.

Key features of the AgGridReturn object include:

  • Grid state: Information about the current configuration and layout of the grid.
  • Selected data: Easy access to rows or cells that the user has selected.
  • Filtered and sorted data: The current state of the data after any user-applied filters or sorting.
  • Edit information: Details about any edits made to the grid’s data.

Let’s explore how to use the AgGridReturn object in practice:

import streamlit as st
import pandas as pd
from st_aggrid import AgGrid, GridOptionsBuilder

# Load your dataset
df = pd.read_json("https://www.ag-grid.com/example-assets/olympic-winners.json")

tabs = st.tabs(['Grid', 'GridOptions', 'AgGridReturn.selected_data', 'AgGridReturn.selected_groupedData'])
with tabs[0]:
    grid_return = AgGrid(df, update_on=["cellClicked"])
with tabs[1]:
    st.write(grid_return.grid_options)
with tabs[2]:
    st.write(grid_return.data)
with tabs[3]:
    st.write(grid_return.event_data)

st.markdown(
"""
## Grid Return
Returned object has some props which we can re-used to build complex vizualization
"""
)
st.write(grid_return)

These three components - AgGrid, GridOptionsBuilder, and AgGridReturn - work in concert to create powerful, interactive tables in Streamlit applications. By leveraging the AgGridReturn object, developers can build more complex and responsive features based on user interactions with the grid.

Advanced Features

Making Cells Editable

One of the powerful features of AgGrid is the ability to make cells editable, allowing users to modify data directly in the table. This functionality is particularly useful for data entry or quick updates to existing datasets.

To enable cell editing:

  1. Set the editable=True parameter when calling the AgGrid function.
  2. Use the AgGridReturn object to access the updated data.

Here’s how it works:

grid_return = AgGrid(df, editable=True)
new_df = grid_return['data']

When cells are edited:

  • The original DataFrame (df) remains unchanged.
  • The data attribute of the grid_return object contains the updated values.
  • You can create a new DataFrame (new_df) with these changes.

This approach allows you to maintain the original data while working with the updated version separately.

Example: Editable Grid with Live Updates

The following example demonstrates an editable grid where changes in the first table automatically update a second table:

import streamlit as st
import pandas as pd
from st_aggrid import AgGrid

# Sample data
df = pd.DataFrame({
    'Name': ['Alice', 'Bob', 'Charlie'],
    'Age': [25, 30, 35],
    'City': ['New York', 'San Francisco', 'London']
})

# First table
st.subheader("Editable Table")
grid_return = AgGrid(df, editable=True)

# Display the newly edited dataframe
st.subheader("Updated Data")
st.dataframe(grid_return['data'])

By leveraging the editable feature of AgGrid along with Streamlit’s reactive framework, you can create dynamic, interactive data editing experiences in your applications.

Advanced Grid Options with JavaScript

AgGrid’s full capabilities can be accessed through its JavaScript API, offering powerful customization options beyond what’s available in the Python interface. While the GridOptionsBuilder is convenient for basic configuration, you can leverage advanced features by working directly with the grid options dictionary or using JavaScript functions.

Direct Dictionary Configuration

You can configure the grid by providing a dictionary that specifies options for both the overall grid and individual columns, or editing the one from the GridOptionsBuilder.build():

import pandas as pd
from st_aggrid import AgGrid

# Sample dataframe
df = pd.DataFrame({
    "sales": [1200, 2300, 1800],
    "profit": [150, 280, 220],
    "category": ["A", "B", "C"]
})

# Comprehensive grid options
grid_options = {
    "columnDefs": [
        {
            "field": "sales",
            "headerName": "Sales ($)",
            "editable": True,
            "filter": "agNumberColumnFilter",
            "valueFormatter": "data.sales.toLocaleString()",
            "cellClass": "number-cell"
        },
        {
            "field": "profit",
            "headerName": "Profit ($)",
            "editable": False,
            "filter": "agNumberColumnFilter",
            "valueFormatter": "data.profit.toLocaleString()",
            "cellStyle": {"color": "#2E7D32"}
        },
        {
            "field": "category",
            "filter": "agSetColumnFilter",
            "filterParams": {
                "values": ["A", "B", "C"],
                "newRowsAction": "keep"
            }
        }
    ],
    "defaultColDef": {
        "flex": 1,
        "minWidth": 150,
        "resizable": True,
    },
    "pagination": True,
    "paginationAutoPageSize": True
}

grid_return = AgGrid(
    df,
    gridOptions=grid_options,
    enable_enterprise_modules=True
)

Using JavaScript Functions with JsCode

If you look at the AgGrid documentation, you’ll notice that many configuration options actually expect JavaScript functions. These functions are run in the user’s browser, and can control everything from cell rendering to value formatting and event handling. To use these JavaScript functions in Streamlit-AgGrid, we use the JsCode class to bridge the Python-JavaScript gap. Here’s an example on modifying the style of an element from Javascript:

from st_aggrid import AgGrid, GridOptionsBuilder, JsCode
import pandas as pd

# Sample data
df = pd.DataFrame({
    "product": ["Laptop", "Phone", "Tablet"],
    "stock": [45, 8, 15],
    "status": ["In Stock", "Low", "Low"]
})

# Custom cell styling based on stock levels
cellStyle = JsCode("""
function(params) {
    if (params.column.colId === 'stock') {
        if (params.value <= 10) {
            return {
                'backgroundColor': '#ffebee',
                'color': '#c62828',
                'fontWeight': 'bold'
            };
        } else if (params.value <= 20) {
            return {
                'backgroundColor': '#fff3e0',
                'color': '#ef6c00'
            };
        }
    }
    return null;
}
""")

# Configure grid options
gb = GridOptionsBuilder.from_dataframe(df)
gb.configure_column("stock", cellStyle=cellStyle)
gb.configure_column("status")
grid_options = gb.build()
grid_return = AgGrid(
    df,
    gridOptions=grid_options,
    allow_unsafe_jscode=True
)

Remember to set allow_unsafe_jscode=True when using JsCode, as it enables execution of custom JavaScript. While this provides powerful customization options, ensure your JavaScript code is properly sanitized and secure for production use.

Custom Cell Rendering

AgGrid allows you to create custom cell renderers to control how your data is displayed in the grid. This powerful feature enables you to create complex interactive components within cells, from custom formatting to interactive buttons and widgets.

Basic Custom Cell Renderer

The simplest way to create a custom cell renderer is by using a JavaScript function that returns HTML content:

from st_aggrid import AgGrid, GridOptionsBuilder, JsCode

# Create a simple cell renderer for formatting currency
currency_renderer = JsCode("""
function(params) {
    return '$' + params.value.toLocaleString('en-US', {
        minimumFractionDigits: 2,
        maximumFractionDigits: 2
    });
}
""")

# Sample data
df = pd.DataFrame({
    'product': ['Widget A', 'Widget B', 'Widget C'],
    'price': [1234.56, 2345.67, 3456.78]
})

# Configure grid options
gb = GridOptionsBuilder.from_dataframe(df)
gb.configure_column('price', cellRenderer=currency_renderer)
grid_options = gb.build()

# Display the grid
grid_return = AgGrid(
    df,
    gridOptions=grid_options,
    allow_unsafe_jscode=True
)

Component-Based Cell Renderer

For more complex scenarios, you can create a cell renderer component class that implements the ICellRendererComp interface. This approach provides more control and allows for interactive elements:

from st_aggrid import AgGrid, GridOptionsBuilder, JsCode

# Create a cell renderer component for a progress bar
progress_renderer = JsCode("""
class ProgressRenderer {
    init(params) {
        this.eGui = document.createElement('div');
        const percentage = params.value;
        this.eGui.innerHTML = `
            <div style="width: 100%; height: 20px; background-color: #eee;">
                <div style="width: ${percentage}%; height: 100%; background-color: #4CAF50;">
                    <span style="padding-left: 5px;">${percentage}%</span>
                </div>
            </div>
        `;
    }

    getGui() {
        return this.eGui;
    }
}
""")

# Sample data
df = pd.DataFrame({
    'task': ['Task A', 'Task B', 'Task C'],
    'progress': [25, 75, 100]
})

# Configure grid options
gb = GridOptionsBuilder.from_dataframe(df)
gb.configure_column('progress', 
                   cellRenderer=progress_renderer,
                   width=200)
grid_options = gb.build()

# Display the grid
grid_return = AgGrid(
    df,
    gridOptions=grid_options,
    allow_unsafe_jscode=True
)

Interactive Cell Renderer with State Management

You can create cell renderers that maintain state and respond to user interactions. Here’s an example of a toggle button cell renderer:

toggle_renderer = JsCode("""
class ToggleRenderer {
    init(params) {
        this.params = params;
        this.eGui = document.createElement('div');
        this.value = params.value;
        
        this.eGui.innerHTML = `
            <label class="switch">
                <input type="checkbox" ${this.value ? 'checked' : ''}>
                <span class="slider round"></span>
            </label>
        `;
        
        this.checkbox = this.eGui.querySelector('input');
        this.checkbox.addEventListener('change', this.onChange.bind(this));
    }

    getGui() {
        return this.eGui;
    }

    onChange(event) {
        this.value = event.target.checked;
        this.params.setValue(this.value);
    }

    destroy() {
        this.checkbox.removeEventListener('change', this.onChange.bind(this));
    }
}
""")

# Add custom CSS for the toggle switch
st.markdown("""
<style>
  ... for the full style, see: https://editor.ploomber.io/editor/f58ce64e
</style>
""", unsafe_allow_html=True)

Best Practices for Custom Cell Renderers

  1. Performance: Keep cell renderers lightweight and efficient, especially when dealing with large datasets.

  2. Cleanup: Always implement the destroy() method to clean up event listeners.

  3. Error Handling: Include error handling in your cell renderers to gracefully handle edge cases:

safe_renderer = JsCode("""
function(params) {
    try {
        // Your rendering logic here
        return params.value ? params.value.toString() : '';
    } catch (error) {
        console.error('Cell rendering error:', error);
        return 'Error';
    }
}
""")

Remember to always set allow_unsafe_jscode=True when using custom cell renderers, as they require JavaScript execution. Additionally, ensure your custom renderers are properly sanitized and secure for production use.

Theme

Streamlit-AgGrid offers various themes to customize the look and feel of your data grid:

  1. streamlit: The default theme, designed to blend seamlessly with Streamlit’s interface.
  2. alpine: A clean, modern theme with a light color scheme.
  3. balham: A professional-looking theme with a balanced color palette.
  4. material: A theme based on Google’s Material Design principles.

Here’s an example of how to implement theme selection in your Streamlit app:

# List of available themes
available_themes = ["streamlit", "alpine", "balham", "material"]

# Create a selectbox for theme selection
selected_theme = st.selectbox("Theme", available_themes)

# Render the AgGrid component with the selected theme
response = AgGrid(
    df,
    ...
    theme=selected_theme,  # Apply the selected theme
)

Data Manipulation in AgGrid

AgGrid provides powerful tools for manipulating and interacting with data directly within the table. The primary method for data manipulation is through row selection, which allows you to work with specific subsets of your data.

Row Selection

Row selection can be configured in several ways to match your application’s needs:

  • Selection Mode: Choose between ‘single’ or ‘multiple’ row selection
  • Checkbox Selection: Add checkboxes for clearer selection UI
  • Click Behavior: Configure how clicks interact with selection

Here’s a simple example of enabling multiple row selection with checkboxes:

gb = GridOptionsBuilder.from_dataframe(df)
gb.configure_selection(
    selection_mode='multiple',     # Enable multiple row selection
    use_checkbox=True             # Show checkboxes for selection
)
grid_options = gb.build()

grid_response = AgGrid(df, gridOptions=grid_options)
selected_rows = grid_response['selected_rows']

Other notable options for grid selection are:

  • rowMultiSelectWithClick: Allow selecting multiple rows by clicking
  • suppressRowDeselection: Prevent deselecting rows with Ctrl+click
  • suppressRowClickSelection: Disable row selection on click

Once rows are selected, you can access them through the selected_rows key in the grid response. This allows you to:

  • Filter your dataset based on selection
  • Perform calculations on selected rows
  • Export selected data
  • Apply bulk updates

Group By

The second option for manipulating the table is to apply group by, which allows us to visualize data in groups, such as Olympic winners by country for example.

To implement this, we can apply the rowGroup property to any column. For example, to group by Country, we can edit the options in GridOptionsBuilder.built():

{ 'field': "country", 'minWidth': 150, 'rowGroup': True, 'hide': True },

This gives us a view organized by country. We can add another level of grouping, such as by age, by simply applying the rowGroup attribute to another column.

groupby = st.checkbox("Group by country and age", False)
gridOptions = {
  'rowSelection': "multiple",
  'groupSelectsChildren': True,
  'autoGroupColumnDef': {
        'headerName': 'My Group',
        'minWidth': 220,
        'cellRendererParams': {
            'suppressCount': False,
            'checkbox': True,
        }
    },
  'columnDefs': [
    { 'field': "athlete", 'minWidth': 150,},
    # First group by by country
    { 'field': "country", 'minWidth': 150, 'rowGroup': groupby, 'hide': groupby},
    # Second group by by age
    { 'field': "age", 'maxWidth': 90, 'rowGroup': groupby, 'hide': groupby},
    { 'field': "year", 'maxWidth': 90 },
    { 'field': "date", 'minWidth': 150 },
    { 'field': "sport", 'minWidth': 150 },
    { 'field': "gold" },
    { 'field': "silver" },
    { 'field': "bronze" },
    { 'field': "total" },
  ],
  'defaultColDef': {
    'flex': 1,
    'minWidth': 100,
  },
};

tabs = st.tabs(['Grid', 'GridOptions', 'AgGridReturn.selected_data'])

with tabs[0]:
  response = AgGrid(df, gridOptions, key='grid1')
with tabs[1]:
  text = code_editor(
    json.dumps(gridOptions, indent=4),
    lang="json",
    height='300px',
    props={'readOnly': False}, 
  ).get('text', '')
  if text != '':
    gridOptions = json.loads(text)
with tabs[2]:
  selected_data = response.selected_data
  
  if selected_data is None:
    st.write('Nothing was selected!')
  else:
    st.write(response.selected_data)
  

Dynamic Column

An interesting feature is to have columns that are not in the original dataframe and are only calculated on the client. This reduces the computation load on the Streamlit server in Python, letting the browser do the calculation with JavaScript. These columns are often referred to as Virtual Columns. Let’s see how we can create one.

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


@st.cache_data()
def generate_sales_data():
    """Generate dataset simulating sales data."""
    np.random.seed(42)
    rows = 50

    # Create a more complex dataset
    df = pd.DataFrame({
        'Product ID': range(1, rows + 1),
        'Category': np.random.choice(['Electronics', 'Clothing', 'Home', 'Sports'], rows),
        'Base Price': np.random.uniform(10, 500, rows).round(2),
        'Quantity Sold': np.random.randint(1, 100, rows),
    })

    return df


def configure_grid_options(df):
    """Configure advanced grid options with multiple features."""
    gb = GridOptionsBuilder.from_dataframe(df)

    # Make some columns editable
    gb.configure_columns(['Base Price', 'Quantity Sold'], editable=True)

    # Add a virtual column (This will be calculated on the client side)
    gb.configure_column(
        'Total Revenue',
        valueGetter="Number(data['Base Price']) * Number(data['Quantity Sold'])",
        cellRenderer="agAnimateShowChangeCellRenderer",
        type=["numericColumn"],
        editable=False,
        valueFormatter="x.toLocaleString('en-US', {style: 'currency', currency: 'USD'})"
    )

    return gb.build()


# Generate data
sales_data = generate_sales_data()

# Configure grid options
grid_options = configure_grid_options(sales_data)

st.subheader('Interactive Sales Data Grid')
st.markdown("""
**Features:**
- Edit Base Price and Quantity Sold
- Automatic Total Revenue calculation
""")

# AgGrid with custom options
ag_return = AgGrid(
    sales_data,
    gridOptions=grid_options,
    height=500,
    theme='alpine',
    allow_unsafe_jscode=True,
    fit_columns_on_grid_load=True,
    reload_data=False
)

With this configuration, we’ve added a dynamic “Total Revenue” column that is calculated and rendered entirely on the client-side, providing real-time updates as the user interacts with the grid.

Filtering, Sorting, and Grouping

One of AgGrid’s most powerful features is its ability to provide users with dynamic control over data presentation. By enabling specific options in the GridOptionsBuilder, you can empower users to filter, sort, and group data directly within the grid interface. Let’s explore how to implement these advanced features:

Enabling User-Controlled Options

To allow users to manipulate data presentation, we can extend our previous example by adding the following configurations to the GridOptionsBuilder:

# Configure row grouping and aggregation
gb.configure_default_column(
    groupable=True,
    value=True,
    enableRowGroup=True,
    aggFunc='sum'
)

# Add filter and sort options
gb.configure_grid_options(
    enableRangeSelection=True,
    enableRangeHandle=True,
    suppressColumnMoveAnimation=False,
    suppressRowClickSelection=False
)

Now hover your mouse over the header, and you’ll be able to select your grouping, filter, and sorting.

Cell Editors

For user provided input, we can drastically improve our quality of life as a developer, by directly restricting the user to some specific format. AgGrid offers powerful customization options for this.

AgGrid provides several built-in cell editors, each designed for different types of data input. You can easily configure these editors using the cellEditor and cellEditorParams options in the GridOptionsBuilder.

Here’s an overview of some commonly used cell editors and how to implement them:

gb = GridOptionsBuilder.from_dataframe(df)
gb.configure_default_column(editable=True)

gb.configure_column(
    "cell_1",
    cellEditor="agRichSelectCellEditor",
    cellEditorParams={"values": ["a", "b", "c"]},
    cellEditorPopup=True,
)

gb.configure_column(
    "cell_2",
    cellEditor='agLargeTextCellEditor',
    cellEditorPopup= True,
    cellEditorParams= {
        'maxLength': 100
    }
)

gb.configure_column(
    "cell_3",
    cellEditor= 'agSelectCellEditor',
    cellEditorParams= {
    'values': ['English', 'Spanish', 'French', 'Portuguese', '(other)'],
        }
)

gb.configure_column(
    "cell_4",
    cellEditor= 'agNumberCellEditor',
    cellEditorParams= {
        'min': 0,
        'max': 100
    })


gb.configure_column(
    "cell_5",
    cellEditor= 'agDateCellEditor',
    cellEditorParams= {
        'min': '2000-01-01',
        'max': '2029-12-31',
        }
    )

gb.configure_grid_options(enableRangeSelection=True)

Nested Table (Master-Detail View)

One of AgGrid’s powerful features is the ability to create nested tables, also known as a Master-Detail view. This functionality is particularly useful when dealing with hierarchical data or when you want to present summary information with the option to drill down into more detailed data.

Imagine a scenario where we have a list of clients, each with multiple associated call records. We want to display a main table (the “master” table) showing a summary of each client’s information, such as their name, account details, and total call statistics. Then, when a user selects a specific client, we want to reveal a nested table (the “detail” table) showing individual call records for that client.

This Master-Detail view allows us to:

  1. Present a clean, high-level overview in the master table
  2. Provide easy access to detailed information without cluttering the main view
  3. Create an intuitive and interactive data exploration experience

Let’s explore how to implement this nested table structure using AgGrid in Streamlit, allowing users to seamlessly navigate between summary client data and detailed call records.

# Example from: https://github.com/PablocFonseca/streamlit-aggrid-examples (Author of Streamlit-aggrid)
import streamlit as st
from st_aggrid import AgGrid, GridOptionsBuilder, JsCode, GridUpdateMode
import pandas as pd
import numpy as np
import requests

url = "https://www.ag-grid.com/example-assets/master-detail-data.json"
r  = requests.get(url)
data = r.json()


df = pd.read_json(url)
AgGrid(df, key="original")
df["callRecords"] = df["callRecords"].apply(lambda x: pd.json_normalize(x))

gridOptions = {
    # MasterDetail: refers to a top level grid called a Master Grid having rows that expand
    "masterDetail": True,
    # Like we saw earlier, and enable the selection of a single column
    "rowSelection": "single",
    # the first Column is configured to use agGroupCellRenderer
    "columnDefs": [
        {
            "field": "name",
            "cellRenderer": "agGroupCellRenderer",
            "checkboxSelection": True,
        },
        {"field": "account"},
        {"field": "calls"},
        {"field": "minutes", "valueFormatter": "x.toLocaleString() + 'm'"},
    ],
    "defaultColDef": {
        "flex": 1,
    },
    # provide Detail Cell Renderer Params
    "detailCellRendererParams": {
        # provide the Grid Options to use on the Detail Grid
        "detailGridOptions": {
            "rowSelection": "multiple",
            "suppressRowClickSelection": True,
            "enableRangeSelection": True,
            "pagination": True,
            "paginationAutoPageSize": True,
            "columnDefs": [
                {"field": "callId", "checkboxSelection": True},
                {"field": "direction"},
                {"field": "number", "minWidth": 150},
                {"field": "duration", "valueFormatter": "x.toLocaleString() + 's'"},
                {"field": "switchCode", "minWidth": 150},
            ],
            "defaultColDef": {
                "sortable": True,
                "flex": 1,
            },
        },
        # get the rows for each Detail Grid
        "getDetailRowData": JsCode(
            """function (params) {
                params.successCallback(params.data.callRecords);
    }"""
        ),
        
    },
    "rowData": data
}

tabs = st.tabs(["Grid", "Underlying Data", "Grid Options", "Grid Return"])

with tabs[0]:
    r = AgGrid(
        None,
        gridOptions=gridOptions,
        allow_unsafe_jscode=True,
        enable_enterprise_modules=True,
        key="an_unique_key",
    )

with tabs[1]:
    st.write(data)

with tabs[2]:
    st.write(gridOptions)

Let me improve the documentation section to make it clearer and more comprehensive.

Responding to User Events

The AG Grid component can respond to various user interactions through event callbacks. One common use case is handling cell clicks. The JavaScript library expects a function to be provided for these callbacks. For example, the onCellClicked event expects a function with the following signature:

function onCellClicked(event: CellClickedEvent<TData, TValue>): void;

This function will be called by AG Grid whenever a cell is clicked, with the event object containing details about the click.

AG Grid provides numerous event callbacks including:

  • onCellClicked: Triggered when a cell is clicked
  • onCellDoubleClicked: Triggered on double-click
  • onCellValueChanged: Triggered when cell content changes
  • And many more available in the AG Grid documentation

To handle these events in Streamlit, we need to write JavaScript code that will execute in the user’s browser. Here’s an example using JsCode to create a cell click handler:

cell_click_handler = JsCode("""
function(params) {
    // Extract relevant information from the event
    const clickedColumn = params.column.colId;
    const clickedRowIndex = params.rowIndex;
    const clickedValue = params.node.data[clickedColumn];
    
    // Display information about the click
    const message = `You clicked on row ${clickedRowIndex}, column ${clickedColumn}, value is ${clickedValue}`;
    alert(message);
    
    // Update the 'clicked' column with the current timestamp
    params.node.setDataValue('clicked', Date.now());
}
""")

Here’s how to integrate the event handler with your grid:

# Initialize the grid builder with your dataframe
grid_builder = GridOptionsBuilder.from_dataframe(df)

# Add a virtual column to store click timestamps
grid_builder.configure_column("clicked", "Clicked Timestamp")

# Configure the grid with the click handler
grid_builder.configure_grid_options(onCellClicked=cell_click_handler)

# Build the final grid options
grid_options = grid_builder.build()

# Create tabs to show the grid and response data
tabs = st.tabs(["Grid", "Response"])

with tabs[0]:
    # Render the grid, enabling unsafe JavaScript execution
    response = AgGrid(
        df,
        grid_options,
        allow_unsafe_jscode=True
    )

with tabs[1]:
    # Display the grid's data including any updates
    st.write(response.data)
    
    # If timestamps exist, show the last click time
    if 'clicked' in response.data.columns:
        last_click = pd.to_datetime(response.data["clicked"], unit='ms').max()
        st.write(f"Last click was at {last_click} UTC")

When a cell is clicked:

  1. The JavaScript event handler is triggered
  2. Information about the clicked cell is captured
  3. An alert displays the click details
  4. The timestamp is updated in the ‘clicked’ column
  5. The response tab shows the updated data including timestamps

Conclusion

AgGrid provides a robust solution for displaying and interacting with complex tabular data in Streamlit. Its flexibility allows developers to create highly customized and interactive data grids that can significantly enhance the user experience of data-driven applications.

To showcase the culmination of these features, let’s look at a final demo that combines many of the elements we’ve discussed. This demo presents a comprehensive AgGrid implementation, demonstrating how these various features can work together in a real-world scenario.

By leveraging these advanced features of AgGrid, you can create sophisticated, interactive data applications that provide users with powerful tools for data analysis and exploration directly in their web browsers.