randyzwitch / streamlit-folium

Streamlit Component for rendering Folium maps
https://folium.streamlit.app/
MIT License
468 stars 176 forks source link

Map Disappearing #173

Closed cgivre closed 6 months ago

cgivre commented 6 months ago

This is a really great plugin!

With that said, I'm encountering an issue where when I try to render a map, it disappears and reappears inconsistently. See video below.

https://www.loom.com/share/79e544d6eea74fd7a898691d217d12a0?sid=d25745ae-b4ea-47a3-8d89-80621fe7c652

For this example, Here are the relevant code sections


def get_pricing():
    """
    This function gets all the pricing information from Prycd and updates the pricing dataframe
    :return:
    """
    global prycd
    apn = apn_textarea

    # Check for empty fields
    if apn is None or len(apn) == 0:
        st.error("Please enter a valid APN.")
        return

    county = county_selectbox
    if county is None or len(county) == 0:
        st.error("Please select a valid county and state.")
        return

    fips = __get_fips_code(county)

    md = f"""
    ## Pricing Data
    The table below displays the pricing for a property located in {county} with assessor's property number {apn}.
    """

    st.markdown(md)

    pricing_results = __get_pricing_info(apn, fips)
    st.dataframe(data=pricing_results,
                 use_container_width=True,
                 hide_index=True,
                 column_config={
                     "price": st.column_config.NumberColumn(label="Price", format="$%.2f"),
                     "price_per_acre": st.column_config.NumberColumn(label="Price Per Acre", format="$%.2f"),
                     "confidence": st.column_config.Column(label="Confidence"),
                     "meta.county": st.column_config.Column(label="County", help="The county where the property is located."),
                     "meta.confidence.Price Coefficient of Variation": st.column_config.NumberColumn(label="Coefficient of Variation"),
                     "meta.confidence.Acreage Coefficient of Variation": st.column_config.NumberColumn(
                         label="Acreage Coefficient of Variation"),
                     "meta.confidence.Number of Total Comps": st.column_config.NumberColumn(
                         label="Total Comps"),
                     "meta.confidence.Number of Sold Comps": st.column_config.NumberColumn(
                         label="Sold Comps")
                 })

    m = folium.Map(location=[39.949610, -75.150282], zoom_start=16)
    folium.Marker(
        [39.949610, -75.150282], popup="Liberty Bell", tooltip="Liberty Bell"
    ).add_to(m)

    # call to render Folium map in Streamlit
    st_data = st_folium(m, width=725)

    # Now populate the comps
    min_acreage = comp_size_range[0]
    max_acreage = comp_size_range[1]

    comps_results = pd.DataFrame(__get_comps(county, state_selectbox, min_acreage, max_acreage))

    # If there are no comps, display a message explaining that and halt.
    if len(comps_results) == 0:
        st.warning("No comps were found meeting this criteria.")
        return

...

# This function is called by a button click in a sidebar here...
with st.sidebar:
    ....
    submit_button = st.button("Submit", on_click=get_pricing)

Any help would be greatly appreciated. I've confirmed the this issue exists in both Firefox and Safari. When I don't include the folium map, the page loads as expected.

BastienGauthier commented 6 months ago

Try returned_object = [] option in st_folium, this should prevent the reloading.

cgivre commented 6 months ago

Try returned_object = [] option in st_folium, this should prevent the reloading.

Unfortunately, that didn't seem to help. I've noticed that the issue only happens when I supply a width parameter.

AnnaWey commented 6 months ago

I have a similar problem, unfortunately not related to the width, but all the maps in my dashboard disappeared recently. In the end, the problem was related to the extra-streamlit-components package, only version 0.1.60 allowed me to display the st_folium maps in the dashboard.

blackary commented 6 months ago

@cgivre I tried to create a reproducible version of your code, and came up with this (with some help from ChatGPT)

import streamlit as st
import pandas as pd
import folium
from streamlit_folium import st_folium

# Dummy variables for the input fields
apn_textarea = "123456789"  # Dummy APN value
county_selectbox = "Sample County, State"  # Dummy county and state
state_selectbox = "Sample State"  # Dummy state
comp_size_range = (1, 10)  # Dummy range for the size of comps

# Dummy functions to simulate functionality
def __get_fips_code(county_name):
    """Dummy function to simulate getting a FIPS code based on a county name."""
    return "12345"  # Dummy FIPS code

def __get_pricing_info(apn, fips):
    """Dummy function to simulate getting pricing information."""
    # Dummy data to represent pricing information
    data = {
        "price": [100000, 150000, 200000],
        "price_per_acre": [10000, 12000, 15000],
        "confidence": ["High", "Medium", "Low"],
        "meta.county": [county_selectbox] * 3,
        "meta.confidence.Price Coefficient of Variation": [0.1, 0.15, 0.2],
        "meta.confidence.Acreage Coefficient of Variation": [0.05, 0.07, 0.1],
        "meta.confidence.Number of Total Comps": [10, 12, 14],
        "meta.confidence.Number of Sold Comps": [5, 7, 9],
    }
    return pd.DataFrame(data)

def __get_comps(county, state, min_acreage, max_acreage):
    """Dummy function to simulate getting comps information."""
    # Dummy data to represent comps information
    data = {
        "county": [county] * 2,
        "state": [state] * 2,
        "min_acreage": [min_acreage] * 2,
        "max_acreage": [max_acreage] * 2,
        "comp_price": [200000, 250000],
    }
    return data

# The main function to get pricing information
def get_pricing():
    global apn_textarea, county_selectbox, state_selectbox, comp_size_range
    apn = apn_textarea

    if apn is None or len(apn) == 0:
        st.error("Please enter a valid APN.")
        return

    county = county_selectbox
    if county is None or len(county) == 0:
        print("Please select a valid county and state.")
        st.error("Please select a valid county and state.")
        return

    fips = __get_fips_code(county)

    md = f"""
    ## Pricing Data
    The table below displays the pricing for a property located in {county} with assessor's property number {apn}.
    """

    print("HERE")
    st.markdown(md)

    pricing_results = __get_pricing_info(apn, fips)
    st.dataframe(pricing_results, use_container_width=True)

    m = folium.Map(location=[39.949610, -75.150282], zoom_start=16)
    folium.Marker(
        [39.949610, -75.150282], popup="Liberty Bell", tooltip="Liberty Bell"
    ).add_to(m)
    st_folium(m, width=725, returned_objects=[])

    min_acreage, max_acreage = comp_size_range
    comps_results = pd.DataFrame(
        __get_comps(county, state_selectbox, min_acreage, max_acreage)
    )

    if len(comps_results) == 0:
        print("No comps were found meeting this criteria.")
        st.warning("No comps were found meeting this criteria.")
        return

    # Additional processing and display of comps_results can be added here

# Streamlit UI elements to trigger get_pricing
with st.sidebar:
    submit_button = st.button("Submit", on_click=get_pricing)

I noticed initially the same thing you observed, which is the map appearing and then disappearing. In my case, adding returned_objects=[] fixed the issue.

However, the larger issue is that using a button for a control of what gets displayed is tricky in Streamlit, because buttons don't preserve their state, and if any other widget (in this case, probably the map itself) generates an app rerun, then the button is no longer pressed and the app reruns as it did before the button was pressed. This is a helpful article on the behavior. https://docs.streamlit.io/library/advanced-features/button-behavior-and-examples

If you want to use a button, you can do something like this

if "button_pressed" not in st.session_state:
    st.session_state["button_pressed"] = False

def button_pressed():
    st.session_state["button_pressed"] = True

# Streamlit UI elements to trigger get_pricing
with st.sidebar:
    submit_button = st.button("Submit", on_click=button_pressed)

if st.session_state["button_pressed"]:
    get_pricing()

I'll close this for now, but if you aren't able to resolve this issue by using the button differently or with the returned_objects=[], feel free to re-open and give a complete reproducible example that shows the behavior you're seeing.