Highlighting choropleth polygon when clicked (or add some marker to it)

I have a choropleth map that covers each state in the U.S. and would like to be able to highlight the polygon when the user clicks on it by way of either increasing the transparency or adding some sort of marker to it (ex: freezing hover tooltip). Is there a way to achieve this?

I came across this article, which is exactly what I’m looking to do but because the geojson file I have is quite large and the graph is redrawing the entire shape due to how the callback works, it takes a while for the transparency to change when clicked - making the app not user-friendly.

Please help!

1 Like

One possible approach to achieve this functionality is to draw the “normal” data in one layer, and then add the selection as another layer on top. I have previously done this in dash-leaflet where the polygons in the selection layer were filtered via the filter option of the GeoJSON component.

https://leafletjs.com/reference-1.7.1.html#geojson-filter

@Emil thank you for the suggestion. Would it be possible for you to show a brief working code for the scenario that I have? Currently, I have a go.Choropleth and go.Scattermapbox as two traces passed into go.Figure to show latlong datapoints underlayed by the choropleth geojson shape files. That part of the code is working well but I just need to add a marker to the polygon when the user clicks it and retrieve the name of that polygon from clickData to match a column in a dataframe, so I can display the data about that polygon.

I have gone through the link you sent me but haven’t been able to achieve something like what I described.

Any help would be much appreciated.

Here is a small example,

import dash_html_components as html
import dash_leaflet as dl
from dash import Dash
from dash.dependencies import Output, Input
from dash_extensions.javascript import assign

geojson_url = "https://raw.githubusercontent.com/PublicaMundi/MappingAPI/master/data/geojson/us-states.json"
# Create javascript function that filters on feature name.
geojson_filter = assign("function(feature, context){return context.props.hideout == feature.id;}")
# Create example app.
app = Dash()
app.layout = html.Div([
    dl.Map(children=[
        dl.TileLayer(),
        # The "normal" geojson layer, colored in grey.
        dl.GeoJSON(url=geojson_url, zoomToBounds=True, options=dict(style=dict(color="grey")), id='geojson'),
        # The "selected" geojson layer, colored in blue (default).
        dl.GeoJSON(url=geojson_url, options=dict(filter=geojson_filter), id="selected")
    ], style={'width': '100%', 'height': '50vh', 'margin': "auto", "display": "block"}, id="map"),
])
# Link click to geojson hideout prop (could also be done with a normal callback, but that is slower).
app.clientside_callback("function(feature){return feature.id;}",
                        Output('selected', 'hideout'), Input('geojson', 'click_feature'))


if __name__ == '__main__':
    app.run_server()

While a large geojson might increase initial load time, I would expect the performance of the click update to remain fast.

click

2 Likes

I’m getting ImportError “cannot import name ‘assign’ from ‘dash_extensions.javascript’” when I copy paste your script and try to run it. I have pip installed dash-leaflet and dash-extensions

The assign function is relatively new. Have you updated to the latest version of dash-extensions (0.0.55)?

Thanks @Emil . Is it possible to pass go.Scattermapbox into dl.Tile? Something like this dl.TileLayer(go.Scattermapbox())? I need to add a scatter plot on a map with multiple traces (i.e. different colored circle) to show locations on a map in addition to the choropleth. Thank you.

This is really cool! Thanks for the example @Emil .

By chance, does anyone know if something similar is possible with ploty choropleth mapbox API?

You can also do it with plotly and px.choropleth_mapbox.

### Version Dash Plotly - Highlighting a selected feature
from dash import Dash, Output, Input, State, dcc, no_update # pip install Dash
from urllib.request import urlopen
import geopandas as gpd # pip install geopandas
import plotly.express as px # pip install plotly
import json

with urlopen('https://raw.githubusercontent.com/PublicaMundi/MappingAPI/master/data/geojson/us-states.json') as response:
    gdf = gpd.GeoDataFrame.from_features(json.load(response))

def create_figure_us_states(): # function to generate first figure
    fig = px.choropleth_mapbox(gdf, 
                                geojson=gdf.geometry.__geo_interface__, 
                                locations=gdf.index,
                                opacity = 0.3,
                                hover_data =["name","density"],
                                mapbox_style="open-street-map",
                                center={"lat": 39.2094813, "lon": -110.1607186},
                                zoom=2)

    fig.update_layout(margin={"r":0,"t":0,"l":0,"b":0})
    fig.update_layout(showlegend=False)
    fig.update_coloraxes(showscale=False)
    return fig


# Create small example app.
app = Dash()
app.layout = dcc.Graph(figure=create_figure_us_states(),config={'displayModeBar': False},id="map-country-us",style={'height':'60vh','width':'100%'})

# Create callback -- Highlighting a selected feature
@app.callback(Output("map-country-us", "figure",allow_duplicate=True),Input('map-country-us', 'clickData'),State('map-country-us','figure'),prevent_initial_call=True)
def update_figure(click_data,figure):
    if click_data is None:
        return no_update

    # Check if properties is in the dict or add it (Need it to create a geodataframe)
    for feature in figure["data"][0]["geojson"]["features"]:
        if 'properties' in feature:
            break
        else:
            feature.update({"properties":{}})

    # Create the geodataframe gdf_
    gdf_ = gpd.GeoDataFrame.from_features(figure["data"][0]["geojson"]["features"]) # Column geometry

    for i in range (0,len(figure["data"][0]["customdata"])):
        gdf_.loc[i,'density'] = figure["data"][0]["customdata"][i][1] #Column Density
        gdf_.loc[i,'name'] = figure["data"][0]["customdata"][i][0] #Column Name
    gdf_["color"]=gpd.GeoDataFrame(figure["data"][0]["z"]) #Column Color
    
    # Extract id of the country with a simple click #
    country_id = click_data['points'][0]['location']
    if gdf_.loc[country_id,'color'] == 0:
        gdf_.loc[country_id,'color'] = 1
    else:
        gdf_.loc[country_id,'color'] = 0

    # Create a new figure with gdf_
    new_fig = px.choropleth_mapbox(gdf_,
                                   geojson=gdf_.geometry.__geo_interface__,
                                   locations=gdf_.index,
                                   opacity = 0.3,
                                   color = gdf_.color,
                                   color_continuous_scale = ['red', 'blue'],
                                   range_color = [0, 1],
                                   hover_data =["name","density"],
                                   mapbox_style="open-street-map",
                                   center={"lat": 39.2094813, "lon": -110.1607186},
                                   zoom=2)
    
    new_fig.update_layout(margin={"r":0,"t":0,"l":0,"b":0})
    new_fig.update_layout(showlegend=False)
    new_fig.update_coloraxes(showscale=False)

    return new_fig
    
### Run app ###
if __name__=='__main__':
    app.run_server(debug=True, port=8070)