Rendering Tabs in plotly- dash and Python

I am relatively new to Plotly and Dash. I recently saw how easy it is to build web analytics dashboards using it. So I am developing a simple app using Tabs to deliver each section content. Unfortunately, the Tab does not render my content. I don’t know the reason for this. I will show each section snippet of my code so that you can understand very clearly as I could not find the issue in between the lines.

I started first by doing the normal stuffs:

from datetime import datetime
from datetime import date
from datetime import  time

import pandas as pd
import plotly.express as px  # (version 4.7.0)
import plotly.graph_objects as go

import dash  # (version 1.12.0) pip install dash
import dash_core_components as dcc
import dash_html_components as html
from dash.dependencies import Input, Output


app = dash.Dash(
    __name__,
    meta_tags=[{"name": "viewport", "content": "width=device-width, initial-scale=1"}],
)

server = app.server
app.config["suppress_callback_exceptions"] = True

Next, I write the functions for the tabs to display in the app using build_tabs shown below

def build_tabs():
    return html.Div(
        id="tabs",
        className="tabs",
        children=[
            dcc.Tabs(
                id="app-tabs",
                value="tab1",
                className="custom-tabs",
                children=[
                    dcc.Tab(
                        id="text1",
                        label="Some Text",
                        value="tab1",
                        className="custom-tab",
                        selected_className="custom-tab--selected",
                    ),
                    dcc.Tab(
                        id="text2",
                        label="Some More Text",
                        value="tab2",
                        className="custom-tab",
                        selected_className="custom-tab--selected",
                    ),
                    dcc.Tab(
                        id="text3",
                        label="More Text",
                        value="tab3",
                        className="custom-tab",
                        selected_className="custom-tab--selected",
                    ),
                    dcc.Tab(
                        id="Text4",
                        label="Some More Text Here",
                        value="tab4",
                        className="custom-tab",
                        selected_className="custom-tab--selected",
                    )                                   
                ],
            )
        ],
    )

I am now trying to structure the layout for the first tab - tab1

def month_options():
    option_dates = []

    for i in range(1,13):
        month_dict = {}
        month_dict["label"] = date(1,i,1).strftime("%B")
        month_dict["value"] = date(1,i,1).month
        option_dates.append(month_dict)

    return option_dates

def tab1_layout():
     return [html.Div(
         [
             html.Div([
                 dcc.Dropdown(
                     id="year",
                     options=[
                         {"label": "2019", "value": 2019},
                         {"label": "2020", "value": 2020}
                     ],
                     value=2020,
                     style={"width": "50%"}
                     ),
                dcc.Dropdown(
                    id="month",
                    options= month_options(),
                    value=3,
                    style={"width": "50%"}
                    ),
                    ], style={"width": "100%", "display": "inline-block"}),

            html.Div([
                dcc.Graph(id="app-content")
                ], style={"width": "100%", "display": "inline-block"})
                ]
                )]

def tab1_plot(selected_year, selected_month):
    tab1_layout()
    df_copy = df.copy()
    df_copy["year"] = df_copy.date.dt.year
    df_copy["month"] = df_copy.date.dt.month
    df_copy = df_copy[(df_copy["month"] == selected_month) & (df_copy["year"] == selected_year)]

    app_dist = df_copy["col"].value_counts()

    fig = px.funnel_area(names=app_dist.keys().tolist(),
                             values=app_dist.tolist(),
                             template='plotly_dark')

    return fig

Now the layout of the app:

app.layout = html.Div(
    id="big-app-container",
    children=[
        build_banner(),
        html.Div(
            id="app-container",
            children=[
                build_tabs(),
                # Main app
                html.Div(id="app-content"),
            ],
        ),
    ],
)

Next the call back

# ------------------------------------------------------------------------------
# Connect the Plotly graphs with Dash Components
@app.callback(
    Output("app-content", "children"),
    [Input("app-tabs", "value"), 
     Input("year", "value"),
     Input("month", "value")],
)
def render_tab_content(tab_switch, selected_year, selected_month):

    if tab_switch == "tab1":
        print(tab_switch)
        return tab1_plot(selected_year = selected_year,
                                    selected_month = selected_month)

if __name__ == '__main__':
    app.run_server(debug=True, use_reloader=False)

Using the debug feature, I see it looks like this:

I wonder why the first tab set as default does not render the required results, this is for tab1 only and I cannot see the output, kindly advise what could be wrong?

Hi and welcome to Dash!

I checked out your code and found a couple of things:

  1. All the callback arguments must be in the layout. See: https://dash.plotly.com/callback-gotchas

Your tab1_plot() is called in the callback, but it contains the tab1_layout(). You can fix t his by just move tab1_layout() to your build_tabs() function like so:

def build_tabs():
    return html.Div(
        id="tabs",
        className="tabs",
        children=[
            dcc.Tabs(
                id="app-tabs",
                value="tab1",
                className="custom-tabs",
                children=[
                    dcc.Tab(
                        # put tab layout function here rather than in tab1_plot()
                        tab1_layout(),
                        id="text1",
                        label="Some Text",
                        value="tab1",
                        className="custom-tab",
                        selected_className="custom-tab--selected",
                    ),type or paste code here
  1. You have duplicate ids “app-content”. You have that defined in the tab1_layout() so you can’t also have it in your main layout.

  2. tab1_plot() returns a figure, so in the callback, it needs to be “figure” and not "children:

@app.callback(
Output(“app-content”, “figure”), ## figure not children

I hope this helps!

Hi @AnnMarieW thank you so much for your help. Let me try this and get back to you shortly

This worked, thank you very much. But the plot is blank. I changed the id for the plot tab1_layout() and the left the id in the main layout as “app-content” could that be the issue?

In the code you provided, there was no df so I couldn’t test that part. Does your tab1_plot actually return a valid figure?

I tested the plot to ensure it works before using it, so It is supposed to return a plot. I updated the tab1_layout() such that the id is now funnel_plot.

Here is the updated tab function:

def build_tabs():

    return html.Div(

        id="tabs",

        className="tabs",

        children=[

            dcc.Tabs(

                id="app-tabs",

                value="tab1",

                className="custom-tabs",

                children=[

                    dcc.Tab(

                        tab1_layout(),

                        id="text1",

                        label="Some Text",

                        value="tab1",

                        className="custom-tab",

                        selected_className="custom-tab--selected",

                    ),

                    dcc.Tab(

                        id="text2",

                        label="Some More Text",

                        value="tab2",

                        className="custom-tab",

                        selected_className="custom-tab--selected",

                    ),

                    dcc.Tab(

                        id="text3",

                        label="More Text",

                        value="tab3",

                        className="custom-tab",

                        selected_className="custom-tab--selected",

                    ),

                    dcc.Tab(

                        id="Text4",

                        label="Some More Text Here",

                        value="tab4",

                        className="custom-tab",

                        selected_className="custom-tab--selected",

                    )                                   

                ],

            )

        ],

    )

I updated my app layout like this:

app.layout = html.Div(

    id="big-app-container",

    children=[

        build_banner(),

        html.Div(

            id="app-container",

            children=[

                build_tabs(),

            ],

        ),

    ],

)

The confusing part now is that which id do I use for my output since the output changes based on the tab selected:

@app.callback(

    Output("funnel_plot", "figure"),

    [Input("app-tabs", "value"), 

     Input("year", "value"),

     Input("month", "value")],

)

def render_tab_content(tab_switch, selected_year, selected_month):

    if tab_switch == "tab1":

        print(tab_switch)

        print(selected_month)

        print(selected_year)

        return tab1_plot(selected_year = selected_year,

                                    selected_month = selected_month)

Impressive! Using the id from tab1 seems to work. Does this mean I will use all the ids from different plots as output as well and use combination of the arguments?

It can be confusing as first because there are several ways to do this depending on how you want to structure your app. I’m sure you checked out https://dash.plotly.com/dash-core-components/tabs

It’s hard to tell what exactly you want to do given the info provided. I’m assuming you will have a different tab_layout() for each tab? In that case you can just have a callback for the inputs in each tab.

1 Like

@AnnMarieW Thank you so much for the insights, much appreciated. I am yet to create a function for each tab. Do you mind doing a pseudo-code of how I should lay it out? I am thrilled with the results already. In my callback. I printed the tabs. but the other tabs are not printing when I click them except the tab1 which is the default. Do you need more info to provide guidance? Pseudo codes are just fine. References as well.

Right now I am feeding in Inputs from tab1. I plan to fill tab1 with lots of plots and tab2, tab3 and tab4 will have plots as well and more than one. How do you recommend I handle this?

For simple apps I like the Method 2. Content as Tab Children https://dash.plotly.com/dash-core-components/tabs and that is what my previous example did.

Since your app is going to be more complex, Method 1 Content as Callback will probably be better. I think this reference material on multi-page apps is really helpful: https://dash.plotly.com/urls

1 Like

Thank you @AnnMarieW Thank you very much for your support. Does this also works for tabs?

So I read the Method 1. reference material on multi-apps as you recommended. Then I approached it differently.

I updated my build_tabs method such that each tab produces an output container:

def build_tabs():

    return html.Div(

        id="tabs",

        className="tabs",

        children=[

            dcc.Tabs(

                id="app-tabs",

                value="tab1",

                className="custom-tabs",

                children=[

                    dcc.Tab(

                        tab1_layout(),

                        id="tab1_plot",

                        label="Some Text 1",

                        value="tab1",

                        className="custom-tab",

                        selected_className="custom-tab--selected",

                    ),

                    html.Div(id='output-tab1'),

                    

                    dcc.Tab(

                        id="tab2",

                        label="Some Text 2",

                        value="tab2_plot",

                        className="custom-tab",

                        selected_className="custom-tab--selected",

                    ),

                    html.Div(id='output-tab2'),

                    

                    dcc.Tab(

                        id="tab3_plot",

                        label="Some Text 3",

                        value="tab3",

                        className="custom-tab",

                        selected_className="custom-tab--selected",

                    ),

                    html.Div(id='output-tab3'),

                    

                    dcc.Tab(

                        id="tab4_plot",

                        label="Some Text 4",

                        value="tab4",

                        className="custom-tab",

                        selected_className="custom-tab--selected",

                    ),

                    html.Div(id='output-tab4'),                                   

                ],

            )

        ],

    )

Then tried to populate the tab1 like this:

@app.callback(

    [Output("output-tab1", "children"),Output("funnel_plot", "figure")],

    [Input("tab1-plot", "value"), 

     Input("year", "value"),

     Input("month", "value")],

)

def render_tab_content(tab_switch, selected_year, selected_month):

    return tab1_plot(selected_year = selected_year,

                                    selected_month = selected_month)

Now the plot is not showing. Could check this out? And kindly advise. Once I get one I can fix the rest. and find my way through.

Thanks

To start, it might be easier for you to make each tab a separate app, then you can use either method 1 or method 2 for the tabs.

Advantage of method1: each app renders only when tab is selected.
Advantage of method2: makes switching tabs really fast because it’s all computed upfront.

Here is a simple example using sample apps from the tutorial https://dash.plotly.com/basic-callbacks for app1 and app2, and the file structure from the multi-page app tutorial:

– app.py
–index_method1.py
–index_method2.py
– apps/app1.py
–apps/app2.py

app.py

import dash

app = dash.Dash(__name__, suppress_callback_exceptions=True)
server = app.server
app.config["suppress_callback_exceptions"] = True

Use either method 1: content as callbacks by running:
index_method1.py

import dash_core_components as dcc
import dash_html_components as html
from dash.dependencies import Input, Output

from app import app
from apps import app1, app2


app.layout = html.Div([
    dcc.Tabs(id='tabs-example', value='tab-1', children=[
        dcc.Tab(label='Tab one', value='tab-1'),
        dcc.Tab(label='Tab two', value='tab-2'),
    ]),
    html.Div(id='tabs-example-content')
])

@app.callback(Output('tabs-example-content', 'children'),
              [Input('tabs-example', 'value')])
def render_content(tab):
    if tab == 'tab-1':       
        return  app1.layout
    elif tab == 'tab-2':
        return app2.layout


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

or use method 2 - content as tab children by running:
index_method2.py

import dash_core_components as dcc
import dash_html_components as html
from dash.dependencies import Input, Output

from app import app
from apps import app1, app2

app.layout = html.Div([
    dcc.Tabs(id='tabs-example', value='tab-1', children=[
        dcc.Tab(app1.layout, label='Tab one'),
        dcc.Tab(app2.layout, label='Tab two'),
    ]),    
])


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

Here are the apps from the tutorial callback section (with slight modifications to make them work as multi-page apps)

app1.py

import dash_core_components as dcc
import dash_html_components as html
from dash.dependencies import Input, Output

import pandas as pd

from app import app

df = pd.read_csv('https://raw.githubusercontent.com/plotly/datasets/master/gapminderDataFiveYear.csv')


layout = html.Div([
    dcc.Graph(id='graph-with-slider'),
    dcc.Slider(
        id='year-slider',
        min=df['year'].min(),
        max=df['year'].max(),
        value=df['year'].min(),
        marks={str(year): str(year) for year in df['year'].unique()},
        step=None
    )
])


@app.callback(
    Output('graph-with-slider', 'figure'),
    [Input('year-slider', 'value')])
def update_figure(selected_year):
    filtered_df = df[df.year == selected_year]
    traces = []
    for i in filtered_df.continent.unique():
        df_by_continent = filtered_df[filtered_df['continent'] == i]
        traces.append(dict(
            x=df_by_continent['gdpPercap'],
            y=df_by_continent['lifeExp'],
            text=df_by_continent['country'],
            mode='markers',
            opacity=0.7,
            marker={
                'size': 15,
                'line': {'width': 0.5, 'color': 'white'}
            },
            name=i
        ))

    return {
        'data': traces,
        'layout': dict(
            xaxis={'type': 'log', 'title': 'GDP Per Capita',
                   'range':[2.3, 4.8]},
            yaxis={'title': 'Life Expectancy', 'range': [20, 90]},
            margin={'l': 40, 'b': 40, 't': 10, 'r': 10},
            legend={'x': 0, 'y': 1},
            hovermode='closest',
            transition = {'duration': 500},
        )
    }

app2.py

import dash_core_components as dcc
import dash_html_components as html
from dash.dependencies import Input, Output

import pandas as pd

from app import app

df = pd.read_csv('https://plotly.github.io/datasets/country_indicators.csv')

available_indicators = df['Indicator Name'].unique()

layout = html.Div([
    html.Div([

        html.Div([
            dcc.Dropdown(
                id='xaxis-column',
                options=[{'label': i, 'value': i} for i in available_indicators],
                value='Fertility rate, total (births per woman)'
            ),
            dcc.RadioItems(
                id='xaxis-type',
                options=[{'label': i, 'value': i} for i in ['Linear', 'Log']],
                value='Linear',
                labelStyle={'display': 'inline-block'}
            )
        ],
        style={'width': '48%', 'display': 'inline-block'}),

        html.Div([
            dcc.Dropdown(
                id='yaxis-column',
                options=[{'label': i, 'value': i} for i in available_indicators],
                value='Life expectancy at birth, total (years)'
            ),
            dcc.RadioItems(
                id='yaxis-type',
                options=[{'label': i, 'value': i} for i in ['Linear', 'Log']],
                value='Linear',
                labelStyle={'display': 'inline-block'}
            )
        ],style={'width': '48%', 'float': 'right', 'display': 'inline-block'})
    ]),

    dcc.Graph(id='indicator-graphic'),

    dcc.Slider(
        id='year--slider',
        min=df['Year'].min(),
        max=df['Year'].max(),
        value=df['Year'].max(),
        marks={str(year): str(year) for year in df['Year'].unique()},
        step=None
    )
])

@app.callback(
    Output('indicator-graphic', 'figure'),
    [Input('xaxis-column', 'value'),
     Input('yaxis-column', 'value'),
     Input('xaxis-type', 'value'),
     Input('yaxis-type', 'value'),
     Input('year--slider', 'value')])
def update_graph(xaxis_column_name, yaxis_column_name,
                 xaxis_type, yaxis_type,
                 year_value):
    dff = df[df['Year'] == year_value]

    return {
        'data': [dict(
            x=dff[dff['Indicator Name'] == xaxis_column_name]['Value'],
            y=dff[dff['Indicator Name'] == yaxis_column_name]['Value'],
            text=dff[dff['Indicator Name'] == yaxis_column_name]['Country Name'],
            mode='markers',
            marker={
                'size': 15,
                'opacity': 0.5,
                'line': {'width': 0.5, 'color': 'white'}
            }
        )],
        'layout': dict(
            xaxis={
                'title': xaxis_column_name,
                'type': 'linear' if xaxis_type == 'Linear' else 'log'
            },
            yaxis={
                'title': yaxis_column_name,
                'type': 'linear' if yaxis_type == 'Linear' else 'log'
            },
            margin={'l': 40, 'b': 40, 't': 10, 'r': 0},
            hovermode='closest'
        )
    }

This works perfectly!