Folium Component (bi-directional communication)(WIP)

Hi,

Love Streamlit. Great work folks.

I was wondering if someone is working in a Folium component. I understand that with st.iframe/st.html we will be able to render static content, but no bi-directional communication with Streamlit. I’m a Data Scientist fluent in Python, Pandas, Numpy and Folium, but little knowledge with javascript. Willing to help but it will be great to make a team with someone who understands fast the structure of the components.

All suggestions are welcome. Anyway, I will try to find time to explore components in the meanwhile.

Have a nice day.

2 Likes

Hi @Juan_Calvo_Ferrandiz, welcome to the Streamlit community! As far as I know, no one has claimed Folium (@tc1 sent some messages to the maintainers of Folium, but not sure where that stands).

In terms of bi-directional communication, the components framework does allow for this. If you are willing to take the lead on creating the plugin (maybe start a Git repo and post it here?), we’re happy to contribute and answer questions. I’m not a JavaScript person either, and I was able to get the basics down yesterday afternoon.

1 Like

Hey @Juan_Calvo_Ferrandiz and welcome the community!

Seconding both of @randyzwitch’s comments:

  • No one [at least that we know of] is working on a Folium component yet, but its one of the most requested libraries in the community.
  • If you’d be willing to take the lead on it, we’d love to help contribute/answer questions.
1 Like

Hi!

Ok. Great to know @tc1 and @randyzwitch ! Let me evaluate this weekend the time I will have in the next weeks.

Please, anyone interested in working in this task, present yourself in this thread o DM me.

Have a nice day everbody!. :smile: :vulcan_salute:

1 Like

Hello :wave:

What kind of bi-directional communication would you use with Folium? (I’m not a Folium user)

However if you want to embed Folium maps statically, st.html() does the work:

import folium
import streamlit as st

MAP_HEIGHT = 500

st.title("Folium showcase")

fig = folium.Figure(height=MAP_HEIGHT)
fig.add_child(folium.Map(location=[45.5236, -122.6750]))

st.html(fig._repr_html_(), height=MAP_HEIGHT+10)

Here’s an explanation on why we need to use folium.Figure(), what is _repr_html_() and why we need to add +10 to st.html()'s height.

Let’s take the first example from Folium’s quickstart guide:

import folium

m = folium.Map(location=[45.5236, -122.6750])
m

Jupyter notebooks display these widgets thanks to _repr_html_(), and Folium’s Map class implements it as expected.

By the way, maybe we could add a new st.ipywidget() or st.plot() to automatically call _repr_*_() functions, which would make streamlit support jupyter’s widgets natively.

That said, let’s try getting the html code of our map and pass it to st.html():

import folium
import streamlit as st

m = folium.Map(location=[45.5236, -122.6750])
st.html(m._repr_html_())

.

It works, but there’s a height issue. We could fix this by passing a height argument to st.html() according to the map’s default height, but it is set as 100%. So we must fix the map’s height ourselves.

I found out that folium.Map() takes a height parameter, but it doesn’t work neither. When you call folium.Map(), internally it creates a folium.Figure() with a height of 100% which will contain the map.

The solution to control the height is to create a folium.Figure() ourselves with the height we want, and then add our map to it.

import folium
import streamlit as st

f = folium.Figure(height=500)
m = folium.Map(location=[45.5236, -122.6750])
f.add_child(m)

st.html(f._repr_html_(), height=500)


It works, but for some reason it’s cropped down there. Actually, I had this glitch when implementing other custom components. I don’t know what causes it yet, but for now to bypass this, I just added +10 to the height of st.html().

1 Like

I could see having bi-directional communication to limit the amount of data being brought into the chart based on the bounding box. So Folium/leaflet could pass back what was in view, then you could use that you limit a pandas dataframe.

Not sure if that would work, but if it did, it would help with scalability.

Exactly. Right now we can use layers, but this just permit using one categorical variable:
1)https://stackoverflow.com/questions/54192523/can-i-create-a-drop-down-list-on-the-folium-map-in-python/54237159
2)https://nbviewer.jupyter.org/github/python-visualization/folium/blob/master/examples/FeatureGroup.ipynb

I have been using FeatureGroupSubGroup(), which permits adding another variable, but the layers don’t cross between variables. I explain everything in this link:
3)https://github.com/python-visualization/folium/issues/1331

It will be great if we could invite Frank Conengmo to this thread, one of the most active developers of Folium at this moment: 4)https://github.com/Conengmo

Being able of making queries to the map, with dropdown buttons or similar, to different categorical variables, would be so awesome.

Alright, so if I understood correctly, the idea would be to use Folium/leaflet’s widgets to do some data filtering instead of relying on streamlit’s widgets?

And as you want something bi-directional, you would like to receive event feedbacks when you toggle a checkbox for instance? Or maybe some data generated by Leaflet in reaction to those checkboxes? Can’t that be managed with streamlit’s widgets only?

I’ve taken your code from your github issue @Juan_Calvo_Ferrandiz and loaded it in streamlit:

Hi Synode! Lets see if I can make myself clear: :smile:

version: 2020/06/05

MAIN IDEA
Being able of filtering data visualization in Folium. (in the example I code in github, filtering CircleMarker() by variables “year” and “type”.). I leave a cleaner version, with no filtering components and plugins of Folium:

import folium
import pandas as pd

# Create df
data = {"id": ["01", "02", "03", "04", "05", "06"],
        "year": [2018, 2019, 2020, 2019, 2019, 2018],
        "type": ["type_a", "type_a", "type_b", "type_b", "type_c", "type_c"],
        "latitude": [-12.1, -12.2, -12.3, -12.4, -12.5, -12.6],
        "longitude": [-72.1, -72.2, -72.3, -72.4, -72.5, -72.6]}

# Respects order
df = pd.DataFrame(data, columns=["id", "year", "type", "latitude", "longitude"])


def _plot_dot(point, map_element,
              radius=4, weight=1,color='black'):
    # group_base = folium.FeatureGroup(name="year_Sin year").add_to(map_element)
    place = map_element

    folium.CircleMarker(location=[point["latitude"], point["longitude"]], radius=radius, weight=weight,
                        color=color, fill=True,
                        fill_color="red",
                        fill_opacity=0.9,
                        tooltip=f'<b>id: </b>{str(point["id"])}'
                                f'<br></br>'f'<b>year: </b>{str(point["year"])}'
                                f'<br></br>'f'<b>type: </b>{str(point["type"])}',

                        popup= f'<b>id: </b>{str(point["id"])}'
                                f'<br></br>'f'<b>year: </b>{str(point["year"])}'
                                f'<br></br>'f'<b>type: </b>{str(point["type"])}'
                        ).add_to(place)

def generate_map(data, filename=None):
    map_element = folium.Map(tiles='cartodbpositron')

    data.apply(_plot_dot, axis=1, args=[map_element])

    map_element.save(filename, close_file=True)

    return map_element


if __name__ == "__main__":
    map_1 = generate_map(df, 'test_maps.html')

HOW? JUST WITH FOLIUM CONTROLS: DONE
At this moment, Folium permits doing this, with one variable, with layers and LayerControl():
1)https://nbviewer.jupyter.org/github/python-visualization/folium/blob/master/examples/FeatureGroup.ipynb

With two variables using FeatureGroupSubGroup(), but the layers don’t cross between variables.
2)https://github.com/python-visualization/folium/issues/1331

HOW? JUST WITH STREAMLIT CONTROLS: TO BE DONE
What I imagine here, without understanding yet Streamlit Components, is to be able of filtering with st.slider() or st.multiselect() or whatever control of Streamlit, making queries to the map. No need of Folium layers. Streamlit components at first, will do all the work.

Basically, being able of doing with Folium maps, what can be achieve with st.map(): https://docs.streamlit.io/en/latest/getting_started.html#draw-charts-and-maps

Later, maybe, we can study the relation of using at the same time both filtering components of Folium and Streamlit, but at first, I think we should start with an easier and simpler case.

NOTES

  • I dont know yet from where Streamlit gets access to Folium map data. I have done to the CircleMarkers, tootips and popups.

COMMENTS

Oh okay, thanks for the clarification!

The bi-directional part confused me because actually you want to use Streamlit widgets to filter what’s rendered with Folium maps. So the way I see it, you’re expecting something uni-directional instead :wink:

It could be possible to get filter values using streamlit widgets and render your map with st.html(), but each time you interact with one of those widgets, the map re-renders.

By looking at Folium’s source code, it works by generating HTML code with Jinja2. What we could do is make a custom component which would render this HTML code, and somehow re-apply the previous zoom level and location on re-render. Another approach would be to select the elements you want to hide by their id or class for instance.

And a third solution: we could make a custom component relying on React Leaflet, but it would be a Leaflet component, not a Folium one.

1 Like

Great! :smile:

Ok. I understand your point of view. I get to the bi-directional conclusion base on this paragraph of the documentation:

“Custom Components are also about iframing content in a Streamlit app. The difference is that Custom Components allow for bi-directional communication with Streamlit, and st.iframe/st.html do not. That is, a Custom Component can receive arguments from the Streamlit app, and return values back to it, while st.iframe and st.html are just about rendering static content.” https://www.notion.so/Components-User-Docs-Public-4cabcc49623e4c8ab71db5a8eb782c3a#22a1eff0581b481d9a190dbcbd23fd12

First ideas pending to deepen in streamlit components:
USER EXPERIENCE KEYS:

  • Ok. I understand. I think that the best approach should be the one that permits using Folium as the most similar way that users do in notebooks.
  • At the same time, using Streamlit, in the most similar way, that with st.map()
  • In summary: I think the objective should be to have a st.folium_map() function that works as st.plotly_chart() for example, and is able of intereacting with streamlit widgets(buttons, sliders, text inputs…)

Please let everyone feel free to contradict me or comment on anything.

But it doesn’t necessarily have to be uni-directional. Who knows what hooks are provided on the JS side…once the map is rendered, maybe you can click on something and do something else, so we send that message back to Streamlit.

The fastest time-to-component is likely to use st.html() and publish this as a standalone library, but it just takes people working with that prototype to really flesh out the requirements.

Aside from showing and hiding layers/markers/circles (let it be with text inputs, checkboxes, dropdowns, … in a cross-layer way), do you have other ideas on how one could possibly interact with Folium maps?

It can be bi-directional indeed, but I don’t know yet how hard it’s gonna be to make streamlit and folium maps interoperate, especially because it implies tweaking folium’s generated HTML. Maybe they are indeed some JS hooks we could use, hopefully, but folium wasn’t designed to let another library change its iframe. At least it doesn’t seem to be. I’ll check this out when I have some time.

1 Like

Hi!!! :vulcan_salute:

For sure. My concatenation of ideas are as follow:

MAIN POINTS STREAMLIT

MAIN POINTS FOLIUM

  • Stability and customizing options.
  • Leaflet.js development.
  • Easy connection of plugins of Leaflet.js
  • FastMarkerCluster () and MarkerCluster() to deal with a lot of rows, elements.

FUTURE LOCATION INTELLIGENCE

  • 5G and loT maturity.
  • Dealing with millions of elements and having the variable time, which makes a lot of snapshots

POWER OF STREAMLIT AND FOLIUM

  • Join previous advantages of 2 products in 1 product.
  • Anticipate how to deal with a lot of elements and snapshots.
  • A must should be to be able to open Folium, once the query is done, to Full-screen mode.
  • Being able to compare 2 maps, with their graphs, with different time snapshots; would be awesome.
  • 3 examples of how I imagine the future of Location Intelligence:
    https://github.com/dabreegster/abstreet/: Its a game, but imagine this with loT.
    https://explorer.morphocode.com/ Masking data, which permits dealing with less data, for better user experience.
    https://www.glidefinder.com/ Awesome BigQueryGIS project:

I will continue thinking. Have a fantastic Saturday. :smile: :herb:

2 Likes

To kick off what’s possible with Streamlit and Folium, I published a preliminary bit of functionality:

Where this package goes next really depends on what it possible with Folium (I’m a beginner, so I don’t know) and where the community adds functionality.

Best,
Randy

4 Likes