Dash-Leaflet map not updating on callback

Hello everyone,

I am not new to dash itself, but to dash-leaflet. I am experiencing a weird behavior when trying to dynamically update circle markers on a map.

I started by creating a leaflet map with circles on them, which have the color blue. Upon clicking a button, some/all of these circles should change their color. But this doesn’t work.

I also created a small example:

from dash import Dash, html, dcc, callback, Output, Input, State, ALL, MATCH, Patch, ctx, dash_table
import dash_leaflet as dl
import dash_bootstrap_components as dbc


def run_dashapp():
    app = Dash()
    maxbounds = [[56,15], [45,8]] # north east, south, west
    
    map = dl.Map([dl.TileLayer()], center=[50.983334,11.633333], zoom=5.5, style={'height': '50vh'}, zoomSnap=0,
                 minZoom=5.5, maxZoom=6, maxBounds=maxbounds, maxBoundsViscosity=1)
    position = {
        '1076': [52.47, 13.46],
        '1193': [51.52, 12.33],
        '1403': [48.57, 9.82],
        '1405': [49.44, 8.38],
        '1413': [48.13, 11.07],
        '1429': [53.11, 7.46],
        '1433': [49.81, 8.63],
        '1437': [49.79, 8.26],
        '1439': [47.67, 11.23],
    }
    for i in position.keys():
        pb = int(i)
        map.children += [dl.CircleMarker(center=position[i], radius=2, opacity=1, color='blue', fill=True, fillColor='blue', fillOpacity=1, children=[dl.Tooltip(content=f"Restaurant: {pb}")])]
    app.layout = html.Div([dbc.Row(html.Div(map), id='map'), dbc.Button('Test', id='button')])

    @app.callback(
        Output('map', 'children'),
        Input('button', 'n_clicks'),
        prevent_initial_call=True
    )
    def update_map(clicks):
        new_map = dl.Map([dl.TileLayer()], center=[50.983334,11.633333], zoom=5.5, style={'height': '50vh'}, zoomSnap=0,
                 minZoom=5.5, maxZoom=6, maxBounds=maxbounds, maxBoundsViscosity=1)
        if clicks >= 0:
            for i in position.keys():
                if i != '1405' and i != '1437':
                    pb = int(i)
                    new_map.children += [dl.CircleMarker(center=position[i], radius=2, opacity=1, color='red', fill=True, fillColor='red', fillOpacity=1, children=[dl.Tooltip(content=f"Restaurant: {pb}")])]
                else:
                    new_map.children += [dl.CircleMarker(center=position[i], radius=2, opacity=1, color='yellow', fill=True, fillColor='yellow', fillOpacity=1, children=[dl.Tooltip(content=f"Restaurant: {pb}")])]
            key = list(position.keys())[clicks%len(position.keys())]
            new_map.children += [dl.Marker(position=position[key])]
        return html.Div(new_map)
    
    app.run(debug=True)

    
if __name__ == "__main__":
    run_dashapp()

I tried multiple ways of fixing it, e.g. taking the original map and only change styles, changing the map itself or the html.Div like in this code example. Nothing worked. In order to check, if the map is updated, i added a marker to the map that should change its location when the callback is executed. The weird thing is, the marker changes its position, the circles however stay with the original color.

Does anyone know what is wrong with my approach or how to solve “changing the colors via a callback”?

Thanks in advance

Path options passed directly to components are considered immutable in React Leaflet, i.e. you can’t modify the color property after the component has been constructed. If you need mutability, you can use the pathOptions property instead. Here is a small example,

import dash_leaflet as dl
from dash import Dash, Input, Output, html

app = Dash()
app.layout = html.Div(
    [
        dl.Map(
            [
                dl.TileLayer(),
                dl.CircleMarker(
                    center=[56, 10],
                    radius=10,
                    pathOptions={"color": "red"},
                    id="marker",
                ),
            ],
            center=[56, 10],
            zoom=6,
            style={"height": "50vh"},
        ),
        html.Button("Test", id="button"),
    ]
)


@app.callback(
    Output("marker", "pathOptions"),
    Input("button", "n_clicks"),
    prevent_initial_call=True,
)
def set_marker_blue(_):
    return {"color": "blue"}


if __name__ == "__main__":
    app.run()
2 Likes