0

I'm creating US map and I want to make it interactive. If the user clicks on certain state it get highlighted, and if another state is clicked it will be highlighted also. Finally if the user clicked on a highligted state, the highlight will be removed. I can not think which property to change in @app.callback to make this work. Here is the code that constructs the us map:

import dash_leaflet as dl
from dash import Dash, html, Output, Input, State
from dash_extensions.javascript import arrow_function

app = Dash()
app.layout = html.Div([
    dl.Map(center=[39, -98], zoom=4, children=[
        dl.TileLayer(),
        dl.GeoJSON(url="/assets/us-states.pbf", format="geobuf", id="states",
                   hoverStyle=arrow_function(dict(weight=5, color='#666', dashArray='')),
                   options=dict(hidden=True)),  # geobuf resource (fastest option)
    ], style={'width': '100%', 'height': '50vh', 'margin': "auto", "display": "block"}, id="map"),
    html.Div(id="state"), html.Div(id="capital")
])


@app.callback(Output("state", "children"), [Input("states", "click_feature")])
def capital_click(feature):
    if feature is not None:
        return f"You clicked {feature['properties']['name']}"

if __name__ == '__main__':
    app.run_server(port=8052, debug=True)

Here is the output of the above code: note that I've clicked California to return the text below the image Map Image

D.L
  • 4,339
  • 5
  • 22
  • 45

1 Answers1

0

There are many ways to realise highlighting of a selected feature. In this answer, I'll be using conditional rendering, i.e. rendering the features in different ways, depending on whether they are selected or not. The hideout property is used to store state, i.e. which features are currently selected. To enable interactivity, the hideout property is updated (via a callback) on click.

import dash_leaflet as dl
from dash import Dash, Output, Input, State
from dash_extensions.javascript import assign

# Color selected state(s) red.
style_handle = assign("""function(feature, context){
    const {selected} = context.hideout;
    if(selected.includes(feature.properties.name)){
        return {fillColor: 'red', color: 'grey'}
    }
    return {fillColor: 'grey', color: 'grey'}
}""")
# Create small example app.
app = Dash()
app.layout = dl.Map([
    dl.TileLayer(),
    dl.GeoJSON(url="/assets/us-states.json", zoomToBounds=True, id="geojson",
               hideout=dict(selected=[]), style=style_handle)
], style={'height': '50vh'}, center=[56, 10], zoom=6)

@app.callback(
    Output("geojson", "hideout"),
    Input("geojson", "n_clicks"),
    State("geojson", "clickData"),
    State("geojson", "hideout"),
    prevent_initial_call=True)
def toggle_select(_, feature, hideout):
    selected = hideout["selected"]
    name = feature["properties"]["name"]
    if name in selected:
        selected.remove(name)
    else:
        selected.append(name)
    return hideout

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

I have added the example to the online documentation.

emher
  • 5,634
  • 1
  • 21
  • 32