Leaflet map inside dmc.AccordionItem

Hi, I’m trying to display a Leaflet map inside a dmc.AccordionItem, and I think hitting a similar problem to the one described at (Show and Tell - Dash Leaflet - #155 by thilles) (Leaflet map inside tabs)

I’ve tried the solution suggested by @Emil (Show and Tell - Dash Leaflet - #156 by Emil). This works the first time the AccordionItem is opened, but goes wrong again if the user closes the AccordionItem, resizes the viewport and then reopens it. (It goes right again if the viewport is then resized while the Item is open) The callback is being triggered but it doesn’t appear to refresh the map.

I’m seeing this kind of thing on the map:

Hoping someone might have a solution. This is the code that produced the image above:

from dash import Dash, html, Input, Output
from dash.exceptions import PreventUpdate
import dash_mantine_components as dmc
from dash_iconify import DashIconify
import dash_leaflet as dl

app = Dash(__name__) 

def cre_main_accordion():
    items_list = [
        {
            "id": "map-item",
            "image": DashIconify(icon="noto-v1:world-map", width=50),
            "label": "Map",
            "description":"Goes wrong if viewport is resized while accordion item is closed",
            "content": dmc.Text("Processing...", size="sm"),
        },
        {
            "id": "selection-item",
            "image": DashIconify(icon="noto:globe-showing-asia-australia", width=50),
            "label": "Select countries",
            "description":"",
            "content": dmc.Text("To be completed...", size="sm")
        },
    ]
    result = dmc.Accordion(
        children=[
            dmc.AccordionItem(
                [
                    create_accordion_label(
                        item["label"], item["image"], item["description"]
                    ),
                    dmc.AccordionPanel(item["content"], id=f"{item['id']}-content"),
                ],
                value=item["id"],
            )
            for item in items_list
        ],
        id="accord",
    )
    return result    

def create_accordion_label(label, image, description):
    return dmc.AccordionControl(
        dmc.Group(
            [
                dmc.Avatar(src=image, radius="xl", size="lg") if type(image)==str else image,
                html.Div(
                    [
                        dmc.Text(label),
                        dmc.Text(description, size="sm", fw=400, c="dimmed"),
                    ]
                ),
            ]
        )
    )

def create_map():
    return dl.Map(
        children=dl.TileLayer(),
        center=[56,10], zoom=6,
        style={'width': '100%', 'height': '50vh'}
    )

app.layout = dmc.MantineProvider(children=[cre_main_accordion()])

@app.callback(
    Output("map-item-content", "children"),
    Input("accord", "value")
)
def refresh_map(val):
    if val == "map-item":
        return create_map()
    else:
        raise PreventUpdate

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

Hey @davidharris

I think this is similar to the issue described here:

Try triggering a dummy resize via callback as shown here.

If I’m not mistaken, this could be a workaround for the issue. Note that you’ll need dash 2.17 for the example below due to the missing Output() for the clientside callback.

from dash import Dash, html, Input, Output, ClientsideFunction
from dash.exceptions import PreventUpdate
import dash_mantine_components as dmc
from dash_iconify import DashIconify
import dash_leaflet as dl

app = Dash(__name__)


def cre_main_accordion():
    items_list = [
        {
            "id": "map-item",
            "image": DashIconify(icon="noto-v1:world-map", width=50),
            "label": "Map",
            "description": "Goes wrong if viewport is resized while accordion item is closed",
            "content": dmc.Text("Processing...", size="sm"),
        },
        {
            "id": "selection-item",
            "image": DashIconify(icon="noto:globe-showing-asia-australia", width=50),
            "label": "Select countries",
            "description": "",
            "content": dmc.Text("To be completed...", size="sm")
        },
    ]
    result = dmc.Accordion(
        children=[
            dmc.AccordionItem(
                [
                    create_accordion_label(
                        item["label"], item["image"], item["description"]
                    ),
                    dmc.AccordionPanel(item["content"], id=f"{item['id']}-content"),
                ],
                value=item["id"],
            )
            for item in items_list
        ],
        id="accord",
    )
    return result


def create_accordion_label(label, image, description):
    return dmc.AccordionControl(
        dmc.Group(
            [
                dmc.Avatar(src=image, radius="xl", size="lg") if type(image) == str else image,
                html.Div(
                    [
                        dmc.Text(label),
                        dmc.Text(description, size="sm", fw=400, c="dimmed"),
                    ]
                ),
            ]
        )
    )


def create_map():
    return dl.Map(
        children=dl.TileLayer(),
        center=[56, 10], zoom=6,
        style={'width': '100%', 'height': '50vh'}
    )


app.layout = dmc.MantineProvider(children=[cre_main_accordion()])


@app.callback(
    Output("map-item-content", "children"),
    Input("accord", "value")
)
def refresh_map(val):
    if val == "map-item":
        return create_map()
    else:
        raise PreventUpdate


app.clientside_callback(
    ClientsideFunction(namespace="clientside", function_name="resize"),
    Input("map-item-content", "children")
)

if __name__ == "__main__":
    app.run(debug=True)

custom.js in assets folder:

if (!window.dash_clientside) {
    window.dash_clientside = {};
}
window.dash_clientside.clientside = {
    resize: function(value) {
        console.log("resizing..."); // for testing
        setTimeout(function() {
            window.dispatchEvent(new Event("resize"));
            console.log("fired resize");
        }, 500);
    return null;
    },
};
3 Likes

Thank you!

That works beautfully, and with a couple of straightforward edits also works without having to re-create the map each time the AccordionItem is opened, which is perfect.

2 Likes