Skip to content

Use st-addgrid dataframes with explosion of data when the cell is clicked #7

Description

@xyffar

METODO ALTERNATIVO COL DOPPIO CLIC. Gestisci il popup che si apre con @st.dialog("Exploded data", width="large")

https://docs.streamlit.io/develop/api-reference/execution-flow/st.dialog#stexperimental_dialog
La tabella è creata col comando AgGrid()

Details

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

# --- 2. Configurazione di Ag-Grid per il Doppio Click ---

# Utilizziamo GridOptionsBuilder per configurare la tabella Ag-Grid
gb = GridOptionsBuilder.from_dataframe(df_parent)

# Configura la selezione della riga (non usiamo checkbox per questo caso)
gb.configure_selection('single', use_checkbox=False)

# Definiamo una funzione JavaScript per gestire l'evento di doppio click sulla riga.
# Questa funzione invierà i dati della riga cliccata a Streamlit tramite Streamlit.setComponentValue.
# Salviamo l'ID del prodotto e i dettagli degli ordini nello session_state di Streamlit.
on_double_click_js = JsCode("""
function(e) {
    if (e.event.detail === 2) { // Controlla se è un doppio click (detail === 2)
        // Invia i dati della riga doppio-cliccata a Streamlit
        // Usiamo e.data per accedere ai dati della riga
        Streamlit.setComponentValue({
            double_clicked_id: e.data['ID Prodotto'], // Puoi mettere anche e.rowIndex per prendere l'indice della riga""
            double_clicked_details: e.data['Dettagli Ordini']
        });
    }
};
""")

# Aggiungiamo il listener per l'evento 'rowDoubleClicked'
# La funzione on_double_click_js verrà eseguita quando una riga viene doppio-cliccata.
gb.on_row_double_clicked(on_double_click_js)

# Costruiamo le opzioni finali della griglia
gridOptions = gb.build()

# --- 3. Visualizzazione del DataFrame Padre ---
st.subheader("Tabella Principale Prodotti")

# Inizializziamo o aggiorniamo lo stato della sessione per i dettagli del doppio click
if 'double_clicked_id' not in st.session_state:
    st.session_state.double_clicked_id = None
    st.session_state.double_clicked_details = None

# Visualizza il DataFrame padre usando AgGrid
# L'output di AgGrid non è direttamente usato qui per la selezione,
# ma l'evento di doppio click viene gestito dal JsCode sopra.
AgGrid(
    df_parent,
    gridOptions=gridOptions,
    data_return_mode='AS_INPUT', # Restituisce i dati come sono stati forniti
    update_mode='MODEL_CHANGED', # Aggiorna la griglia quando il modello interno cambia (es. selezione)
    fit_columns_on_grid_load=True,
    allow_unsafe_jscode=True,    # Permette l'esecuzione del codice JavaScript personalizzato
    enable_enterprise_modules=False, # Imposta su True se hai la licenza Ag-Grid Enterprise
    height=300,
    width='100%',
    key='parent_dataframe' # Chiave unica per il widget AgGrid
)

# --- 4. Visualizzazione del DataFrame Figlio al Doppio Click ---
if st.session_state.double_clicked_id is not None:
    st.divider()
    st.subheader(f"Dettagli Ordini per Prodotto ID: {st.session_state.double_clicked_id}")

    # Converti la stringa JSON del DataFrame figlio in un DataFrame Pandas
    try:
        child_df_json = st.session_state.double_clicked_details
        child_df = pd.read_json(child_df_json)

        with st.expander(f"Mostra Dettagli Ordini per Prodotto {st.session_state.double_clicked_id}", expanded=True):
            st.dataframe(child_df, use_container_width=True, hide_index=True)
            st.markdown(f"Questi sono gli ordini associati al **Prodotto ID {st.session_state.double_clicked_id}**.")
    except Exception as e:
        st.error(f"Errore nel caricamento dei dettagli: {e}")
        st.write("Assicurati che i dati dei dettagli siano in un formato JSON valido.")

METODO ALTERNATIVO COL DOPPIO CLIC. Gestisci il popup che si apre con @st.dialog("Exploded data", width="large")

https://docs.streamlit.io/develop/api-reference/execution-flow/st.dialog#stexperimental_dialog
La tabella è creata con una nuova classe che eredita da AgGrid

Details

# --- Nuova Classe: ClickableDataFrame ---
class ClickableDataFrame:
    def __init__(self, df: pd.DataFrame, key: str, fields_to_extract: list = None, extract_index: bool = True, **kwargs):
        """
        Inizializza una tabella Ag-Grid cliccabile che gestisce gli eventi di doppio click.

        Args:
            df (pd.DataFrame): Il DataFrame da visualizzare.
            key (str): Una chiave unica per il widget Streamlit.
                       Questa chiave verrà usata anche come prefisso per le chiavi in st.session_state.
            fields_to_extract (list, optional): Una lista di nomi di colonne (stringhe) da estrarre
                                                dalla riga doppio-cliccata e salvare in session_state.
                                                Se None o vuota, salva l'intera riga (e.data). Defaults to None.
            extract_index (bool): Se True, l'indice posizionale della riga (e.rowIndex)
                                  verrà salvato in session_state. Defaults to True.
            **kwargs: Argomenti aggiuntivi da passare direttamente a AgGrid (es. height, width, etc.).
        """
        self.df = df
        self.key = key
        self.fields_to_extract = fields_to_extract
        self.extract_index = extract_index
        self.aggrid_kwargs = kwargs

        # Inizializza lo stato della sessione per questa istanza se non esiste già
        if f'{self.key}_double_clicked_index' not in st.session_state:
            st.session_state[f'{self.key}_double_clicked_index'] = None
            st.session_state[f'{self.key}_double_clicked_data'] = None

        self._configure_grid()

    def _configure_grid(self):
        """
        Configura le opzioni di Ag-Grid, inclusa la logica JavaScript per il doppio click.
        """
        gb = GridOptionsBuilder.from_dataframe(self.df)
        gb.configure_selection('single', use_checkbox=False) # Nessuna checkbox di selezione

        # JavaScript per gestire l'evento di doppio click sulla riga.
        # Questa funzione invierà l'indice e l'intera riga di dati a Streamlit.
        # La logica di estrazione dei campi specifici avverrà in Python.
        on_double_click_js = JsCode("""
        function(e) {{
            if (e.event.detail === 2) {{ // Controlla se è un doppio click (detail === 2)
                Streamlit.setComponentValue({{
                    index: e.rowIndex, // Indice posizionale della riga
                    data: e.data      // Tutti i dati della riga
                }});
            }}
        }};
        """)
        gb.on_row_double_clicked(on_double_click_js)

        self.grid_options = gb.build()

    def display(self):
        """
        Visualizza la tabella Ag-Grid e gestisce la risposta dal componente JavaScript.
        """
        grid_response = AgGrid(
            self.df,
            gridOptions=self.grid_options,
            data_return_mode='AS_INPUT', # Restituisce i dati così come sono stati forniti
            update_mode='MODEL_CHANGED', # Aggiorna la griglia quando il modello interno cambia (es. selezione)
            fit_columns_on_grid_load=True,
            allow_unsafe_jscode=True,    # Permette l'esecuzione del codice JavaScript personalizzato
            enable_enterprise_modules=False, # Imposta su True se hai la licenza Ag-Grid Enterprise
            key=self.key, # Chiave unica per il widget AgGrid
            **self.aggrid_kwargs
        )

        # Processa la risposta inviata dal codice JavaScript
        # La risposta personalizzata di JsCode è disponibile in grid_response.custom_response
        if grid_response.custom_response:
            clicked_index = grid_response.custom_response.get('index')
            clicked_data = grid_response.custom_response.get('data')

            if clicked_index is not None and clicked_data is not None:
                # Salva l'indice se richiesto
                if self.extract_index:
                    st.session_state[f'{self.key}_double_clicked_index'] = clicked_index
                else:
                    st.session_state[f'{self.key}_double_clicked_index'] = None # Reset se non richiesto

                # Estrai solo i campi specificati, altrimenti salva l'intera riga
                if self.fields_to_extract:
                    extracted_data = {
                        field: clicked_data.get(field)
                        for field in self.fields_to_extract
                        if field in clicked_data # Assicurati che il campo esista nei dati della riga
                    }
                    st.session_state[f'{self.key}_double_clicked_data'] = extracted_data
                else:
                    st.session_state[f'{self.key}_double_clicked_data'] = clicked_data
            else:
                # Resetta lo stato se la risposta è incompleta (non dovrebbe accadere con il JS attuale)
                st.session_state[f'{self.key}_double_clicked_index'] = None
                st.session_state[f'{self.key}_double_clicked_data'] = None

    def get_double_clicked_data(self):
        """
        Restituisce l'indice e i dati (o i campi estratti) della riga doppio-cliccata.
        Restituisce (None, None) se nessuna riga è stata doppio-cliccata o se lo stato è stato resettato.
        """
        return (
            st.session_state.get(f'{self.key}_double_clicked_index'),
            st.session_state.get(f'{self.key}_double_clicked_data')
        )

# --- 2. Creazione e Visualizzazione della Tabella Cliccabile ---
st.subheader("Tabella Principale Prodotti")

# Istanzia la nostra nuova classe ClickableDataFrame
# Specifichiamo i campi che vogliamo estrarre dalla riga doppio-cliccata.
# Se non specifichi 'fields_to_extract', verrà salvata l'intera riga.
# Se 'extract_index' è False, l'indice non verrà salvato.
clickable_table = ClickableDataFrame(
    df=df_parent,
    key='parent_table', # Chiave unica per questa istanza della tabella
    fields_to_extract=['ID Prodotto', 'Nome Prodotto', 'Dettagli Ordini'], # Campi da estrarre
    extract_index=True, # Salva l'indice posizionale
    height=300,
    width='100%'
)

# Visualizza la tabella
clickable_table.display()

if st.session_state.get(f'{clickable_table.key}_double_clicked_index') is not None:
    show_details_dialog()


@st.dialog("Exploded data", width="large")
def show_details_dialog():
    st.subheader(f"Dettagli Ordini per Riga (Indice Posizionale): {double_clicked_index}")

    try:
        child_df_json = double_clicked_data.get('Dettagli Ordini')
        if child_df_json:
            child_df = pd.read_json(child_df_json)

            st.dataframe(child_df, use_container_width=True, hide_index=True)
            st.markdown(f"Questi sono gli ordini associati alla **riga con indice posizionale {double_clicked_index}** (Prodotto ID: {product_id_from_clicked_data}).")
        else:
            st.warning("I dettagli degli ordini non sono disponibili per la riga selezionata. Assicurati che 'Dettagli Ordini' sia incluso in 'fields_to_extract' o che 'fields_to_extract' sia None.")
    except Exception as e:
        st.error(f"Errore nel caricamento dei dettagli: {e}")
        st.write("Assicurati che i dati dei dettagli siano in un formato JSON valido.")

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions