Chase P.
Chase P.

Reputation: 41

Trying to Keep Session Variables in Streamlit

I have a Streamlit app that is displaying messages being ingested by a Kafka Consumer that represent game plays being produced from the NBA API. I am successfully displaying the game plays in real-time in a dataframe in my Streamlit App but I also want to display the current players on the floor. However, when I have used the session state variables to show the current players, it creates a new row of data whenever a change occurs (see screenshot below). It's odd because the current score has the same logic but it is staying on it's own line.enter image description here

with st.container():
    for game in games:
        game_id = game[2]
        game_time_str = game[4]
        home_team_id = game[6]
        away_team_id = game[7]
        home_team = get_team_name(home_team_id)
        away_team = get_team_name(away_team_id)
        label = f"{home_team} vs. {away_team} ({game_time_str.strip()})"
        st.button(label=label, key=f"game_{game_id}", on_click=set_game_state, args=(game_id, label, home_team, away_team), use_container_width=True)




if 'selected_game_id' in st.session_state:
    if st.button("View Game Plays"):
        
        game_over = is_game_over(st.session_state.selected_game_id)

        # Create placeholders for the score, DataFrame, and refresh timer
        score_placeholder = st.empty()
        df_placeholder = st.empty()
        refresh_placeholder = st.empty()

        def update_display():
            columns = [
                        'game_id', 'action_number', 'clock', 'timeActual', 'period', 'periodType',
                        'team_id', 'teamTricode', 'actionType', 'subType', 'descriptor',
                        'qualifiers', 'personId', 'x', 'y', 'possession', 'scoreHome', 'scoreAway', 'description'
                    ]
            all_plays = get_all_plays_from_db(st.session_state.selected_game_id)
            if not game_over:
                consumer.subscribe(['nba-plays'])
                start_time = time.time()
                
                while time.time() - start_time < 3:
                    json_obj = json.loads(boxscore.BoxScore(game_id=st.session_state.selected_game_id).get_json())
                    home_players = [player['name'] for player in json_obj['game']['homeTeam']['players'] if player['oncourt'] == '1']
                    away_players = [player['name'] for player in json_obj['game']['awayTeam']['players'] if player['oncourt'] == '1']

                    if 'current_players' not in st.session_state:
                        st.session_state.current_players = {}
                    
                    if st.session_state.current_players.get('home') != home_players or st.session_state.current_players.get('away') != away_players:
                        st.session_state.current_players['home'] = home_players
                        st.session_state.current_players['away'] = away_players
                        st.markdown(f'<b>{st.session_state.home_team}</b>: {", ".join(home_players)}', unsafe_allow_html=True)
                        st.markdown(f'<b>{st.session_state.away_team}</b>: {", ".join(away_players)}', unsafe_allow_html=True)
                    msg = consumer.poll(0.1)
                    if msg is None:
                        continue
                    if msg.error():
                        if msg.error().code() == KafkaError._PARTITION_EOF:
                            continue
                        else:
                            st.error(f"Error: {msg.error()}")
                            break

                    play_data = json.loads(msg.value().decode('utf-8'))
                    if play_data['gameId'] == st.session_state.selected_game_id:
                        play_tuple = tuple(play_data.get(col, None) for col in columns)
                        all_plays.append(play_tuple)

                if all_plays:
                    
                    df = pd.DataFrame(all_plays, columns=columns)

                    # Convert 'qualifiers' from JSON string to list
                    df['qualifiers'] = df['qualifiers'].apply(lambda x: json.loads(x) if isinstance(x, str) else x)

                    # Clean up 'clock' field
                    df['clock'] = df['clock'].apply(lambda x: x.replace('PT', '').replace('M', ':').replace('S', '') if isinstance(x, str) else x)

                    # Add 'Court Coordinates' column
                    df['Court Coordinates'] = df.apply(lambda row: f"({row['x']}, {row['y']})" if pd.notnull(row['x']) and pd.notnull(row['y']) else None, axis=1)

                    # Rename columns
                    df = df.rename(columns={
                        'period': 'Period',
                        'clock': 'Time Remaining',
                        'teamTricode': 'Team',
                        'description': 'Play Description',
                        'actionType': 'Action Type',
                        'subType': 'Action Subtype',
                        'descriptor': 'Descriptor',
                        'qualifiers': 'Tags'
                    })

                    # Reorder columns
                    columns_order = [
                        'Period', 'Time Remaining', 'Team', 'Play Description', 'Action Type', 'Action Subtype',
                        'Descriptor', 'Tags', 'Court Coordinates', 'scoreHome', 'scoreAway'
                    ]
                    df = df[columns_order]
                    
                    df = df.sort_values(by=['Period', 'Time Remaining'], ascending=[False, True])

                    if not df.empty:
                        latest_play = df.iloc[0]
                        score_placeholder.info(f"Current Score: {st.session_state.home_team} {latest_play['scoreHome']} - {st.session_state.away_team} {latest_play['scoreAway']}")

                        df.drop(columns=['scoreHome', 'scoreAway'], inplace=True)
                        # Display selected columns
                        
                        
                        df_placeholder.dataframe(df, hide_index=True, use_container_width=True)
                    else:
                        st.write("No plays found for this game.")
                else:
                    st.write("No plays found for this game in the database.")
            else:
                consumer.subscribe(['nba-plays'])  # Subscribe to the nba-plays topic
                plays = []
                start_time = time.time()

                while time.time() - start_time < 3:  # Poll for 5 seconds
                    msg = consumer.poll(0.1)
                    if msg is None:
                        continue
                    if msg.error():
                        if msg.error().code() == KafkaError._PARTITION_EOF:
                            continue
                        else:
                            st.error(f"Error: {msg.error()}")
                            break

                    play_data = json.loads(msg.value().decode('utf-8'))
                    if play_data['gameId'] == st.session_state.selected_game_id:
                        plays.append(play_data)

                if plays:
                    df = pd.DataFrame(plays, columns=columns)
                    if df['period'].iloc[0] == 2 and df['description'].iloc[0] == 'Period End':
                        st.write("HALFTIME! Waiting 5 Minutes Until Next Refresh")
                        time.sleep(300)
                    # Clean up 'clock' field if it exists
                    if 'clock' in df.columns:
                        df['clock'] = df['clock'].apply(lambda x: x.replace('PT', '').replace('M', ':').replace('S', '') if isinstance(x, str) else x)

                    # Add 'Court Coordinates' column if 'x' and 'y' exist
                    if 'x' in df.columns and 'y' in df.columns:
                        df['Court Coordinates'] = df.apply(lambda row: f"({row['x']}, {row['y']})" if pd.notnull(row['x']) and pd.notnull(row['y']) else None, axis=1)

                    # Rename columns
                    column_mapping = {
                        'period': 'Period',
                        'clock': 'Time Remaining',
                        'teamTricode': 'Team',
                        'description': 'Play Description',
                        'actionType': 'Action Type',
                        'subType': 'Action Subtype',
                        'descriptor': 'Descriptor',
                        'qualifiers': 'Tags'
                    }
                    df = df.rename(columns={k: v for k, v in column_mapping.items() if k in df.columns})

                    # Reorder columns
                    columns_order = [
                        'Period', 'Time Remaining', 'Team', 'Play Description', 'Action Type', 'Action Subtype',
                        'Descriptor', 'Tags', 'Court Coordinates', 'scoreHome', 'scoreAway'
                    ]
                    df = df[[col for col in columns_order if col in df.columns]]

                    if not df.empty:
                        latest_play = df.iloc[0]
                        if 'scoreHome' in latest_play and 'scoreAway' in latest_play:
                            score_placeholder.header(f"Current Score: {st.session_state.home_team} {latest_play['scoreHome']} - {st.session_state.away_team} {latest_play['scoreAway']}")

                        # Display selected columns
                        df_placeholder.dataframe(df, hide_index=True)
                    else:
                        df_placeholder.dataframe(df, hide_index=True)
                        df_placeholder.write("No new plays in the last 5 seconds.")
                else:
                    df_placeholder.write("No new plays in the last 5 seconds.")

        # Polling loop
        while not game_over:
            update_display()
            time.sleep(1)# Update the display
            game_over = is_game_over(st.session_state.selected_game_id)

        st.write("Game has ended. Final plays displayed above.")

Upvotes: 1

Views: 62

Answers (0)

Related Questions