emilhe / dash-leaflet

MIT License
204 stars 33 forks source link

map does not render in dbc accordion component #204

Open hiker32 opened 9 months ago

hiker32 commented 9 months ago

When leaflet component is added within the dbc accordion component, the map is not rendered properly and only grey blocks are visible. However, if you move around your browser window and open the tab with maps again, the leaflet element works completely fine. There seems to be some issue with how leaflet interacts with style arguement and currently similar issue have been observed with dbc tab container as well.

guidocioni commented 5 months ago

I also encountered this issue today.

It seems that when the Accordion starts collapsed then the Map element does not render properly.

emilhe commented 5 months ago

It is known issue that the map does not render properly, if it is initially hidden. I believe that is what you are seeing with the accordion component as well. The workaround is to delay adding the map until after the container is visible.

guidocioni commented 5 months ago

It is known issue that the map does not render properly, if it is initially hidden. I believe that is what you are seeing with the accordion component as well. The workaround is to delay adding the map until after the container is visible.

Do you have an example showing how to design such a callback?

emilhe commented 5 months ago

The exact syntax depends a bit on the component at hand (e.g. tabs, accordions, ...). For this particular case, it would be along these lines,

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

MAP_VALUE = "map"

app = Dash()
app.layout = html.Div(
    accordion := dmc.Accordion(
        children=[
            dmc.AccordionItem(
                [
                    dmc.AccordionControl("Map"),
                    container := dmc.AccordionPanel(),
                ],
                value=MAP_VALUE,
            )
        ],
    )
)

@app.callback(Output(container, "children"),
              Input(accordion, "value"),
              State(container, "children"))
def update(value, children):
    if value != MAP_VALUE:
        raise PreventUpdate
    # Only initialize once.
    if value is None or children is not None:
        raise PreventUpdate
    # Initialize map here.
    return dl.Map(dl.TileLayer(), center=[56, 10], zoom=6, style={'height': '50vh'})

if __name__ == '__main__':
    app.run_server()
guidocioni commented 5 months ago

The exact syntax depends a bit on the component at hand (e.g. tabs, accordions, ...). For this particular case, it would be along these lines,

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

MAP_VALUE = "map"

app = Dash()
app.layout = html.Div(
    accordion := dmc.Accordion(
        children=[
            dmc.AccordionItem(
                [
                    dmc.AccordionControl("Map"),
                    container := dmc.AccordionPanel(),
                ],
                value=MAP_VALUE,
            )
        ],
    )
)

@app.callback(Output(container, "children"),
              Input(accordion, "value"),
              State(container, "children"))
def update(value, children):
    if value != MAP_VALUE:
        raise PreventUpdate
    # Only initialize once.
    if value is None or children is not None:
        raise PreventUpdate
    # Initialize map here.
    return dl.Map(dl.TileLayer(), center=[56, 10], zoom=6, style={'height': '50vh'})

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

A wild Walrus appears...😄 thanks, I'll try and let you know

guidocioni commented 5 months ago

When I understood what you really meant in your first message I realized the solution was easier than I thought. I just modified the callback that was creating the map by adding the accordion active item as input so that it only gets created when it is active.

# layout
dbc.Accordion(id='map-accordion',
    children=[
    dbc.AccordionItem(
        children=html.Div(id='map-div'),
        title='Map (click to show)')
],
start_collapsed=True)

# callback
@callback(
    Output("map-div", "children"),
    Input("map-accordion", "active_item"),
)
def create_map(item):
    if item is not None and item == 'item-0':
        return make_map()
    else:
        raise PreventUpdate

Now I only need to learn what dash mantine is :)

I think you can close this as we do have a valid solution to fix the issue.

guidocioni commented 5 months ago

Sorry @emilhe last doubt. How I can I make the Map component fill up the entire space of the AccordionItem? Or at least to reduce the padding...

Screenshot 2024-01-29 at 10 16 21

I tried to modify the style of the Div holding the Map but that doesn't seem to affect it... I believe there is some padding set for the AccordionItem but I cannot really find it...

guidocioni commented 5 months ago

Sorry @emilhe last doubt. How I can I make the Map component fill up the entire space of the AccordionItem? Or at least to reduce the padding...

Screenshot 2024-01-29 at 10 16 21

I tried to modify the style of the Div holding the Map but that doesn't seem to affect it... I believe there is some padding set for the AccordionItem but I cannot really find it...

Just leaving this here in case someone is interested.

You just have to create a new custom class in the css stylesheet

.map-accordion-body-padding .accordion-body {
  padding-top: 2px;
  padding-left: 2px;
  padding-right: 2px;
  padding-bottom: 2px;
}

and then use map-accordion-body-padding in the class_name of the AccordionItem