Encountered some Problems when using dbc.Tabs

Hi members,

Need your help~
When I use dbc.Tabs with pages and dash-leaflet, I met two problems.

The first issue is that when using the additional input of the Pages Routing to switch languages, the active tab become empty.

\downarrow

And I saw that someone has encountered this difficulty before.

:meat_on_bone:


Another issue is that when I put dl.Map into the tab 2, large blocks of gray appear.

However, this situation will return to normal after I resize the browser window.

Hi @Emil , do you know if this is a known bug?
Can I add a client-side callback to fix or bypass this issue?

Here’s my code.

app.py

from dash import Dash, html, dcc, page_container, page_registry, Input
import dash_bootstrap_components as dbc
import dash_leaflet as dl


def create_app():

    app = Dash(
        use_pages=True,
        external_stylesheets=[dbc.themes.BOOTSTRAP],
        routing_callback_inputs={
            # The language will be passed as a `layout` keyword argument to page layout functions
            "language": Input("language", "value"),
        },
    )

    TRANSLATIONS = {
        "en": {
            "title": "My app",
        },
        "fr": {
            "title": "Mon app",
        },
    }
    DEFAULT_LANGUAGE = "en"

    app.layout = html.Div(
        [
            html.H1("Multi-page app with Dash Pages"),
            dcc.Dropdown(
                id="language",
                options=[
                    {"label": "English", "value": "en"},
                    {"label": "Français", "value": "fr"},
                ],
                value=DEFAULT_LANGUAGE,
                persistence=True,
                clearable=False,
                searchable=False,
                style={"minWidth": 150},
            ),
            html.Div(
                [
                    html.Div(
                        dcc.Link(
                            f"{page['name']} - {page['path']}",
                            href=page["relative_path"],
                        )
                    )
                    for page in page_registry.values()
                ]
            ),
            page_container,
        ]
    )

    return app


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

pages/test.py

from dash import html, register_page
import dash_bootstrap_components as dbc
import dash_leaflet as dl

register_page(__name__)


def layout(language: str = "en", **_kwargs):
    tab1_content = dbc.Card(
        dbc.CardBody(
            [
                html.P("This is tab 1!", className="card-text"),
                dbc.Button("Click here", color="success"),
            ]
        ),
        className="mt-3",
    )

    tab2_content = dbc.Card(
        dbc.CardBody(
            [
                html.P("This is tab 2!", className="card-text"),
                dbc.Row(
                    [
                        dbc.Col(
                            [
                                dl.Map(
                                    dl.TileLayer(),
                                    center=[56, 10],
                                    zoom=6,
                                    style={"height": "300px"},
                                )
                            ]
                        )
                    ]
                ),
                dbc.Row(
                    [
                        dbc.Col(
                            [
                                dbc.Button("Click here", color="success"),
                            ]
                        )
                    ]
                ),
            ]
        ),
        className="mt-3",
    )

    tab3_content = dbc.Card(
        dbc.CardBody(
            [
                html.P("This is tab 3!", className="card-text"),
                dbc.Button("Don't click here", color="danger"),
            ]
        ),
        className="mt-3",
    )

    tabs = dbc.Tabs(
        [
            dbc.Tab(tab1_content, label="Tab 1"),
            dbc.Tab(tab2_content, label="Tab 2"),
            dbc.Tab(tab3_content, label="Tab 3"),
        ]
    )
    layout = html.Div(tabs)
    return layout

Any help is appreciated.

Hi @stu, concerning the resize I could imagine doing this clientside:

It’s ugly but might be a workaround.

1 Like

Hey @stu,

again maybe a bit ugly but a workaround. Adding a dcc.Store() and a callback fixes the active_tab problem.

app.py:

from dash import Dash, html, page_container, callback, State, dcc, Input, Output, page_registry, Input
import dash_bootstrap_components as dbc
# import dash_leaflet as dl


def create_app():

    app = Dash(
        use_pages=True,
        external_stylesheets=[dbc.themes.BOOTSTRAP],
        routing_callback_inputs={
            # The language will be passed as a `layout` keyword argument to page layout functions
            "language": Input("language", "value"),
        },
    )

    TRANSLATIONS = {
        "en": {
            "title": "My app",
        },
        "fr": {
            "title": "Mon app",
        },
    }
    DEFAULT_LANGUAGE = "en"

    app.layout = html.Div(
        [
            html.H1("Multi-page app with Dash Pages"),
            dcc.Dropdown(
                id="language",
                options=[
                    {"label": "English", "value": "en"},
                    {"label": "Français", "value": "fr"},
                ],
                value=DEFAULT_LANGUAGE,
                persistence=True,
                clearable=False,
                searchable=False,
                style={"minWidth": 150},
            ),
            html.Div(
                [
                    html.Div(
                        dcc.Link(
                            f"{page['name']} - {page['path']}",
                            href=page["relative_path"],
                        )
                    )
                    for page in page_registry.values()
                ]
            ),
            page_container,
        ]
    )

    @callback(
        Output('mem', 'data'),
        Output('tabs', 'active_tab'),
        Input('tabs', 'active_tab'),
        State('mem', 'data')
    )
    def do(active_tab, data):
        if active_tab is None:
            return data, data
        return active_tab, active_tab

    return app


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

test.py:

from dash import html, register_page, dcc
import dash_bootstrap_components as dbc

register_page(__name__)


def layout(language: str = "en", **_kwargs):
    tab1_content = dbc.Card(
        dbc.CardBody(
            [
                html.P("This is tab 1!", className="card-text"),
                dbc.Button("Click here", color="success"),
            ]
        ),
        className="mt-3",
    )

    tab2_content = dbc.Card(
        dbc.CardBody(
            [
                html.P("This is tab 2!", className="card-text"),
                dbc.Row(
                    [
                        dbc.Col(
                            [
                                html.Div('haha')
                                # dl.Map(
                                #     dl.TileLayer(),
                                #     center=[56, 10],
                                #     zoom=6,
                                #     style={"height": "300px"},
                                # )
                            ]
                        )
                    ]
                ),
                dbc.Row(
                    [
                        dbc.Col(
                            [
                                dbc.Button("Click here", color="success"),
                            ]
                        )
                    ]
                ),
            ]
        ),
        className="mt-3",
    )

    tab3_content = dbc.Card(
        dbc.CardBody(
            [
                html.P("This is tab 3!", className="card-text"),
                dbc.Button("Don't click here", color="danger"),
            ]
        ),
        className="mt-3",
    )

    tabs = dbc.Tabs(
        id='tabs',
        children=[
            dbc.Tab(tab1_content, label="Tab 1"),
            dbc.Tab(tab2_content, label="Tab 2"),
            dbc.Tab(tab3_content, label="Tab 3"),
            dcc.Store('mem')
        ]
    )

    return html.Div(tabs)

The callback could be client-side, obviously.

1 Like

Hi bro,

no worries. guess what? I got it fixed by just putting the content as a callback, as well as, by which I got a performance improvement.

from dash import html, register_page, callback, Input, Output
import dash_bootstrap_components as dbc
import dash_leaflet as dl

register_page(__name__)


def layout(language: str = "en", **_kwargs):

    tabs = dbc.Tabs(
        [
            dbc.Tab(tab_id="tab-1", label="Tab 1"),
            dbc.Tab(tab_id="tab-2", label="Tab 2"),
            dbc.Tab(tab_id="tab-3", label="Tab 3"),
        ],
        id="tabs",
        active_tab="tab-1",
    )

    layout = html.Div([tabs, html.Div(id="tabs-content")])

    return layout


tab1_content = dbc.Card(
    dbc.CardBody(
        [
            html.P("This is tab 1!", className="card-text"),
            dbc.Button("Click here", color="success"),
        ]
    ),
    className="mt-3",
)

tab2_content = dbc.Card(
    dbc.CardBody(
        [
            html.P("This is tab 2!", className="card-text"),
            dbc.Row(
                [
                    dbc.Col(
                        [
                            dl.Map(
                                dl.TileLayer(),
                                center=[56, 10],
                                zoom=6,
                                style={"height": "300px"},
                            )
                        ]
                    )
                ]
            ),
            dbc.Row(
                [
                    dbc.Col(
                        [
                            dbc.Button("Click here", color="success"),
                        ]
                    )
                ]
            ),
        ]
    ),
    className="mt-3",
)

tab3_content = dbc.Card(
    dbc.CardBody(
        [
            html.P("This is tab 3!", className="card-text"),
            dbc.Button("Don't click here", color="danger"),
        ]
    ),
    className="mt-3",
)


@callback(
    Output("tabs-content", "children"),
    Input("tabs", "active_tab"),
)
def update(at):
    match at:
        case "tab-1":
            return tab1_content
        case "tab-2":
            return tab2_content
        case "tab-3":
            return tab3_content
        case _:
            return html.Div("Error.")

And I really love those transforms Emil wrote. Blocking callbacks saved me from getting cluttered with animations/transitions with a bunch of filter components and charts today.

1 Like

I can confirm that this is a know issue - if the map is hidden on initial render, the bug you reported occurs. The recommended workaround is to delay the rendering of the map until it’s actually visible - but I see that you figured that out already :smiley:

1 Like

hi @Emil , seems like I’ve hit a bit of a roadblock when it comes to choropleth map. The snag is with the performance, as I need to dynamically fetch some data to fill in the colors, much like the density property you mentioned in your example but in a dynamic way. I gave GeoPandas a shot to merge the data, but it’s not quite cutting it.

Any hints on how to best handle this? I’m keen to find the most efficient way to load the polygons statically while dynamically filling in the colors.

Thx for any insights you can offer!

import dash_leaflet as dl
from dash import Dash, html, Output, Input
import requests  # Import requests library for making HTTP requests
from dash.dependencies import Output, Input
from dash_extensions.javascript import assign

# Function to fetch dynamically generated data from an external source.
def fetch_dynamic_data():
    # Example: Fetch data from an API
    response = requests.get('https://example.com/api/dynamic_data')
    if response.status_code == 200:
        dynamic_data = response.json()
        return dynamic_data
    else:
        print("Error fetching dynamic data:", response.status_code)
        return None

# Function to calculate color based on dynamically generated data.
def calculate_color(value):
    # Your logic to determine color based on the dynamically generated data.
    # Example logic: If value > 500, return 'red', else return 'green'
    return 'red' if value > 500 else 'green'

# Updated style_handle function to use dynamically generated color.
style_handle = assign("""
function(feature, context){
    const {style} = context.hideout;  // get style props from hideout
    const value = feature.properties["your_dynamic_data_property"];  // Get dynamically generated data property.
    style.fillColor = calculateColor(value);  // Calculate color based on dynamically generated data.
    return style;
}""")

# Create geojson with updated style_handle.
geojson = dl.GeoJSON(url="/assets/us-states.json",
                     style=style_handle,
                     zoomToBounds=True,
                     zoomToBoundsOnClick=True,
                     hideout=dict(style=dict(weight=2, opacity=1, color='white', dashArray='3', fillOpacity=0.7)),
                     id="geojson")

# Create app.
app = Dash(prevent_initial_callbacks=True)
app.layout = dl.Map(children=[
    dl.TileLayer(), geojson
], style={'height': '50vh'}, center=[37.8, -96], zoom=4)

# Callback to update the map with dynamically generated data.
@app.callback(Output("geojson", "hideout"),
              [Input("interval-component", "n_intervals")])
def update_map(n_intervals):
    # Fetch dynamically generated data
    dynamic_data = fetch_dynamic_data()
    if dynamic_data:
        # Update the hideout property of geojson with dynamically generated data
        return {"style": {"your_dynamic_data_property": dynamic_data}}
    else:
        # If fetching data fails, return the current hideout value
        return dash.no_update

# Run the app.
if __name__ == '__main__':
    app.run_server()