Folium: Highlight map elements on mouseover/click

Created on 3 Feb 2016  路  26Comments  路  Source: python-visualization/folium

Hi team,

Thank you for developing folium, it's immensely helpful. We're using it to display routes on a map. To declutter some complicated routing maps, I would like to be able to highlight tagged groups of polylines (corresponding to a group of routes) by mouseover/clicking on them. I found some related leaflet examples online, but I am uncertain whether that is possible folium.

enhancement

Most helpful comment

How I can make this work? I tried using "highlight=true" as an argument for choropleth and got the following:

_TypeError: choropleth() got an unexpected keyword argument 'highlight'_

All 26 comments

Hi team,

Thank you for developing folium, it's immensely helpful. We're using it to display routes on a map.

Nice application.

To declutter some complicated routing maps, I would like to be able to highlight tagged groups of polylines (corresponding to a group of routes) by mouseover/clicking on them. I found some related leaflet examples online, but I am uncertain whether that is possible folium.

That is not possible right now, but it would be a nice feature to have. Not sure if that is a simple thing to add though...

Thanks @ocefpaf. Anything I could do to help with that, given a good knowledge of Python and no knowledge of Javascript? If you gave me an idea on how you would tackle this problem, maybe I could give it a try?

@agravier

Thanks for your (welcome) help. To give a hint, here is a hack of folium.GeoJson template that (almost) do the job.

m = folium.Map([46,-100], zoom_start=4)

g = folium.GeoJson(open('../folium/examples/us-states.json'),
                  style_function=lambda x: {'fillColor':'#ffff00'})

g._template = Template("""
            {% macro script(this, kwargs) %}
                var {{this.get_name()}} = {};

                {{this.get_name()}}.style = function(feature) {return feature.properties.style;};

                {{this.get_name()}}.highlightStyle = function(feature) {return {
                        weight: 5,
                        color: '#666',
                        dashArray: '',
                        fillOpacity: 0.7
                        };
                    };

                {{this.get_name()}}.onEachFeature = function onEachFeature(feature, layer) {
                    layer.on({
                        mouseover: function(e) {
                            e.target.setStyle({{this.get_name()}}.highlightStyle(e.target.feature));},
                        mouseout: function(e) {
                            {{this.get_name()}}.geoJson.resetStyle(e.target);},
                        click: function(e) {
                            {{this._parent.get_name()}}.fitBounds(e.target.getBounds());}
                        });
                    };

                {{this.get_name()}}.geoJson = L.geoJson(
                    {% if this.embed %}{{this.style_data()}}{% else %}"{{this.data}}"{% endif %},{
                        style : {{this.get_name()}}.style,
                        onEachFeature: {{this.get_name()}}.onEachFeature
                        })
                    .addTo({{this._parent.get_name()}});
            {% endmacro %}
""")

m.add_child(g)

m

Maybe it would worth creating a highlight_style_function argument in the same fashion as style_function that would write the highlight style in the GeoJson (through style_data method). That way,

 {{this.get_name()}}.highlightStyle = ...

would simply become

 {{this.get_name()}}.highlightStyle = function(feature) {return feature.properties.highlightStyle;};

BTW, it seems there is a bug somewhere : when you "hover" MultiPolygon states (Alaska, Washington, Virginia...), the resetStyle method does not do it's job completely. I cannot figure out where I'm wrong ; maybe there's some trick (or bug?) in Leaflet. Anyway, it can be workarouned in putting more information in style_function.

Thank you @BibMartin. Absorbing the info. I'll be back with a PR or with questions (or most likely with both).

Regarding the multi-polygon areas not being reset on mouseout, I'm way out of my depth in the js pool, but comparing the Leaflet example trace to something similar to your hack above, I noticed that in the Folium case, setAttribute is not executed. Not sure what that implies. Screenshot below.

screenshot 2016-02-05 00 02 02

I would love to work on this. Is anyone doing so at the moment?

I'm not doing anything @joshuacano. Looking forward to your work!

Just finishing the initial pass on this, Quick question. @ocefpaf Should this be the default behavior? Or should we pass in an option or flag to activate this?

Just finishing the initial pass on this

Awesome PR @joshuacano! Sorry for the delay to review it. (Day job keeping me away from fun work :smile:)

Quick question. @ocefpaf Should this be the default behavior? Or should we pass in an option or flag to activate this?

Optional flag for sure. I believe you already have that in your PR, right?

How I can make this work? I tried using "highlight=true" as an argument for choropleth and got the following:

_TypeError: choropleth() got an unexpected keyword argument 'highlight'_

Does anyone have insight on this?

http://stackoverflow.com/questions/42767565/folium-custom-pop-up

I have similar issues in that I'd love to have a popup displayed with data embedded in the GeoJSON properties field on mouseover. Thanks!

@BibMartin I know this was a long time ago, but I had the same problem with reset. What I ended up doing to apply the same style as before:

function resetHighlight(e) {
var layer = e.target;

              layer.setStyle({
                  weight: 1,
                  color: '#666',
                  dashArray: '',
                  fillOpacity: 0.7,
                  opacity: 1
              });
              info.update();
          }

After reading through this conversation, I still do not understand if there now is the possibility to highlight/popup on a mouse-over event. This new highlight-function is not in the docstring of the classes chloropleth or GeoJson (I commented here: https://github.com/joshuacano/folium/commit/62a46d3154c996eb396dd807538f0e6559053703 and I have no idea how to use it.

Is it now possible to popup on mouseover?

We don't have any nice example illustrating the use of the highlight_function option. PRs welcome :smile:

Done. #630

Hi, not sure if I should open a new issue... any plan to do the same for Marker and CircleMarker elements?

I'm trying to accomplish the same thing as the example posted originally, but instead of population density, I'd be looking to list population overall per state. Hover over a state to see the population, and so on.

It would also be great to be able to toggle this interactive layer on/off like any other Feature.Group, but I envision this to be a default enabled layer.

Has this been accomplished yet? I couldn't quite make sense of it one way or another, but would appreciate any help!

@Azryael sounds like you want GeoJsonTooltip? Or is your question more Marker specific?

@jtbaker Not marker specific. Will GeoJsonToolTip give me this effect? https://leafletjs.com/examples/choropleth/

Apologies for the questions, I've only been messing around with Python for the last two days, so it's all very new to me. Just getting by off of what Java and C/C# I know.

@Azryael From #375 a few days ago:

Check the docstring for GeoJsonTooltip to reference its functionality with the help command, it covers the functionality pretty well.

I have a a notebook example in my geojsonmarkers PR (not yet merged) that demonstrates it how the GeoJsonTooltip can be used. The marker argument is not yet active (I need to fix a few things) but the tooltipping will work the same way for a Choropleth/Polygon map.

You can see it here: https://nbviewer.jupyter.org/github/jtbaker/folium/blob/geojsonmarker/examples/GeoJsonMarkersandTooltips.ipynb

It's a slightly different effect, but offers the same functionality - display underlying data for locations on mouseover. Leaflet has several options availablle in this feature, most of which are available in the Tooltip/GeoJsonTooltip kwargs. You can find their documentation here: https://leafletjs.com/reference-1.0.0.html#tooltip

@jtbaker Oh that's looking good! Perhaps I can find a way to statically embed the information popup in a corner as is done on that example I linked, but this it looking incredibly promising and a step in the right direction! For a moment I was toying with recreating everything using pure HTML and leaflet.js... but then I'd have to handle the packaging, which just isn't as streamlined as outputting the .py to an HTML document that is already 90% styled.

I had to take a step back to do some home improvement projects, but now I'm hoping to be able to grab my laptop and sit outside by the fire and work on this some more.

Just at a glance, would it be difficult to set a fill color within that layer where it still functions as a choropleth map as well? This functionality of hovering over each state to see the population should ideally be tied to the choropleth layer. Sorry if the answer is obvious! It's not quite jumping out at me at the moment.

Thank you so much for this, though, this is a HUGE leap in the right direction that I want to go!

@Azryael It'll do the exact same thing with the Choropleth polygon layer with the style_function argument - they're both geojson layers with the same layer of abstraction in the features - just remember you have to get past the properties key in the conditional logic to get to your attributes.

something like a lambda:

folium.GeoJson(..., style_function = lambda feat: {'fillColor':'blue' if feat['properties']['Party']=='Democrat' else 'red' if feat['properties']['Party']=='Republican' else 'grey'})

or a normal function:

def style(feat):
    partycolors ={
        "Democrat":"blue",
        "Republican":"red"
        "Independent":"grey"
    }
    properties = feat['properties']
    return {"fillColor":partycolors[properties['Party']]}

folium.GeoJson(..., style_function=style)

Right now my GeoJson marker argument is unmerged as I have some work to do on it so Polygons/Polylines are the only current GeoJsons that can be customized like that.

@jtbaker Ahh, okay. I see. Right now I simply have my choropleth defined as this:

    map.choropleth(
            geo_data='data/us_states.json',
            name='Veteran Population by State',
            data=state_data,
            columns=['State','Veteran Population'],
            key_on='feature.id',
            fill_color="YlGn",
            fill_opacity=0.7,
            line_opacity=0.2,
            legend_name="Veteran Population by State"
      )

But restyling using folium.GeoJson seems I would move away from the map.choropleth arguement, correct?

Tooltip doesn't have functionality in the choropleth method right now - it's a bit of a unicorn in terms of how the rest of the folium API work and TBH I'm not that familiar with it - but it looks like it's doing a merge on some keys under the hood using the GeoJson or TopoJson classes to construct the content after the merge is done, so I think we could possibly add a tooltip arg here and pass it thru to those constructors, if the attributes we want to query/display are still in our properties, it should be good.

I'll check it out. I really appreciate the help! For the first time I'm finding myself enjoying programming again enough to really get back into it.

I will put what I have so far into a notebook before I make any changes so you can give it a look as well.

@jtbaker Alright, I think I got it on Juptyer here:

https://nbviewer.jupyter.org/github/Azryael/Map-Project/blob/master/Map.ipynb

I've not made any changes yet following what you've shown me, this is just a baseline to show where I'm currently at.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

achourasia picture achourasia  路  15Comments

reaganch picture reaganch  路  13Comments

wangchenwc picture wangchenwc  路  18Comments

xhx509 picture xhx509  路  15Comments

themiurgo picture themiurgo  路  19Comments