pythonplotly-dashdash-leaflet

How to add a dash component as a dash-leaflet custom control?


I'm slowly discovering the dash-leaflet interface coming from ipyleaflet widget lib and there are still some tasks that I don't manage to achieve.

Here I would like to add a dash componenent (in this case a dropdown select) and place it on the map as control so it fits consistently with the leaflet layout when the map is resized.

Here is a map:

import dash_leaflet as dl
from dash import Dash

colorscale = ['red', 'yellow', 'green', 'blue', 'purple']  # rainbow
app = Dash()
app.layout = dl.Map(
    [dl.TileLayer()], 
    center=[56, 10], 
    zoom=6,  
    style={'height': '100vh'},
)

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

And here is the little select that I would like to place on it:

from dash import dcc

dropdown = dcc.Dropdown(
    component_label="toto-label",
    id="toto-id",
    options=[
        {"label": "NDVI (8)", "value": "ndvi8"},
        {"label": "NDVI (16)", "value": "ndvi16"},
    ],
    value="ndvi16",
)

enter image description here

EDIT

to clarify exactly what I'm looking for, I want to mimick what the WidgetControl is doing in ipyleaflet i.e. placing the component in the controls hierarchy with something like "topleft" and without creating dedicated css styling.


Solution

  • import dash
    import dash_leaflet as dl
    from dash import html, dcc, Output, Input
    
    app = dash.Dash()
    
    dropdown = dcc.Dropdown(
        id="layer-dropdown",
        options=[
            {"label": "OpenStreetMap", "value": "https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"},
            {"label": "Stamen Terrain", "value": "https://stamen-tiles.a.ssl.fastly.net/terrain/{z}/{x}/{y}.jpg"},
            {"label": "Stamen Toner", "value": "https://stamen-tiles.a.ssl.fastly.net/toner/{z}/{x}/{y}.png"},
            {"label": "CartoDB Dark", "value": "https://{s}.basemaps.cartocdn.com/dark_all/{z}/{x}/{y}{r}.png"},
        ],
        value="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png",
        style={"width": "200px"},
    )
    
    dropdown_control = html.Div(
        dropdown,
        style={
            "position": "absolute",
            "top": "10px",   # Adjust this to move it lower or higher
            "right": "10px",  # Position it like a Leaflet control
            "zIndex": "1000",  # Ensure it appears above the map
            "background": "white",
            "padding": "5px",
            "border-radius": "5px",
            "box-shadow": "2px 2px 5px rgba(0,0,0,0.3)",
        },
    )
    
    app.layout = html.Div(
        [
            html.Div(
                [
                    dl.Map(
                        [
                            dl.TileLayer(id="base-layer", url=dropdown.value),  # Dynamic Tile Layer
                        ],
                        id="map",
                        center=[56, 10],
                        zoom=6,
                        style={"height": "100vh", "width": "100%"},
                    ),
                ],
                style={"position": "relative"},
            ),
            dropdown_control,  # Add the dropdown separately on top of the map
        ],
        style={"position": "relative"},
    )
    
    @app.callback(
        Output("base-layer", "url"),  # Update the TileLayer URL
        Input("layer-dropdown", "value"),
    )
    def update_map_layer(selected_layer):
        return selected_layer
    
    if __name__ == "__main__":
        app.run(debug=True)