Multi-Page Dash App

Hi,

I am trying to follow the code found on Dash-by-Plotly/app.py at master · Coding-with-Adam/Dash-by-Plotly · GitHub

I copied it word for word to try and set up the page before I insert my own pages. But for some reason the links for page 2 and 3 don’t work. I get the same page shown for all 3 links and only the url changes:



image

Here is the code:

import dash
from dash import html, dcc

app = dash.Dash(name, use_pages=True)

app.layout = html.Div(
[
# main app framework
html.Div(“Python Multipage App with Dash”, style={‘fontSize’:50, ‘textAlign’:‘center’}),
html.Div([
dcc.Link(page[‘name’]+" | ", href=page[‘path’])
for page in dash.page_registry.values()
]),
html.Hr(),

    # content of each page
    dash.page_container
]

)

if name == “main”:
app.run_server(host = ‘0.0.0.0’, port = 20172)

pg1 code:

import dash
from dash import dcc, html
import plotly.express as px

dash.register_page(name, path=‘/’)

df = px.data.gapminder()

layout = html.Div(
[
dcc.Dropdown([x for x in df.continent.unique()], id=‘cont-choice’, style={‘width’:‘50%’}),
dcc.Graph(id=‘line-fig’,
figure=px.histogram(df, x=‘continent’, y=‘lifeExp’, histfunc=‘avg’))
]
)

pg2 code:

import dash
from dash import dcc, html
import plotly.express as px

dash.register_page(name)

df = px.data.tips()

layout = html.Div(
[
dcc.RadioItems([x for x in df.day.unique()], id=‘day-choice’),
dcc.Graph(id=‘bar-fig’,
figure=px.bar(df, x=‘smoker’, y=‘total_bill’))
]
)

pg3 code:

import dash
from dash import dcc, html

dash.register_page(name)

layout = html.Div(
[
dcc.Markdown(‘# This will be the content of Page 3’)
]
)

1 Like

Hi @dgudhka, next time when posting your code please use a code block so it shows properly. To do that place three back ticks, then go down a line and paste the code then down a line and 3 more back ticks which is the ` symbol.

Everything works for me if I setup the folders and files in the correct order. It’s a bit hard to tell what your issue is with the way you pasted the code in and without knowledge of your folder structure.

If I take your code and fix up the formatting issues do to pasting the code without a code block. Then setup my folder structure as:

Root Folder of App
  -app.py
  -pages folder
     -pg1
     -pg2
     -pg3

Everything then works, so here’s the layout to use:

App.py

import dash
from dash import html, dcc

app = dash.Dash(__name__, use_pages=True)

app.layout = html.Div(
    [
        # main app framework
        html.Div("Python Multipage App with Dash", style={'fontSize':50, 'textAlign':'center'}),
        html.Div([
            dcc.Link(page['name']+"  |  ", href=page['path'])
            for page in dash.page_registry.values()
        ]),
        html.Hr(),

        # content of each page
        dash.page_container
    ]
)


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

\pages\pg1.py

import dash
from dash import dcc, html
import plotly.express as px

dash.register_page(__name__, path='/')

df = px.data.gapminder()

layout = html.Div(
[
dcc.Dropdown([x for x in df.continent.unique()], id='cont-choice', style={'width':'50%'}),
dcc.Graph(id='line-fig',
figure=px.histogram(df, x='continent', y='lifeExp', histfunc='avg'))
]
)

\pages\pg2.py

import dash
from dash import dcc, html
import plotly.express as px

dash.register_page(__name__)

df = px.data.tips()

layout = html.Div(
[
dcc.RadioItems([x for x in df.day.unique()], id='day-choice'),
dcc.Graph(id='bar-fig',
figure=px.bar(df, x='smoker', y='total_bill'))
]
)

\pages\pg3.py

import dash
from dash import dcc, html

dash.register_page(__name__)

layout = html.Div(
[
dcc.Markdown(‘# This will be the content of Page 3’)
]
)
2 Likes

I see!

I was also trying to add a loading sign to my table when it first loads and when someone presses the ‘Update’ button.

\\

lets user update data themselves

html.Button('Update', id='interval-component',style = {'marginLeft':'10px'}),
dbc.Spinner(html.Div(id='loading-output')),
# html.Div(id='page-1-content')

])

callback takes in 4 input variables including one to refresh every 15 seconds

@callback([
# Output(‘page-1-content’, ‘children’),
Output(‘loading-output’,‘children’),
],
[
Input(‘dropvalue’,‘value’),
Input(‘datevalue’,‘value’),
Input(‘notional’,‘value’),
Input(‘interval-component’, ‘n_clicks’)
])

datatable is using the dataframe from the query

def update_output(dropval,dateval,notional,n):

\\

But this only shows the loading sign and my table does not show up. Is this not how to do it? I thought it’s just supposed to be one output field with the id in the spinner in app.layout. I didn’t include an if and return in the callback function since my code doesn’t change with any buttons it just has to rerun/refresh.

Never mind - it worked.

Only thing is that code reloads for every input cell while I only want it to update when user clicks the ‘Update’ Button. How can you do that?

Here is my layout:

    page_1_layout = html.Div([
    dcc.Link(' Go to Single Stock', href='/single-stock'),
    html.Br(),
    dcc.Link(' Go back to Home', href='/moc-home'),    
    html.Hr(),
    html.H1(id = 'H1', children = ['Top Imbalances'], style = {'textAlign':'center', 'marginTop':40,'marginBottom':40, 'color':'rgb(0, 128, 0)'}),
    dbc.Row([

        # default date is last business day date
        dbc.Col([
            html.Label(id = 'Date', children = ' Date: ',style = {'color':'rgb(0, 128, 0)','marginLeft':10}),
            dcc.Input(id='datevalue',
                value = datetime.strftime(yesterday, '%Y%m%d'),
                style = {'textAlign':'center', 'marginLeft':'10px'},
                type='text'
            ),
        ],width = 3),

        # notional default is 500,000 thus only rows with notional>500000 show up by default
        dbc.Col([
            html.Label(id = 'Notional', children = ' Notional($): ',style = {'color':'rgb(0, 128, 0)'}),
            dcc.Input(id='notional',
                value = 500000,
                style = {'textAlign':'center','marginLeft':'10px'},
                type='number'
            ),
        ],width = {'size':4,'offset':2}),

        # allows user to fetch the first or last batch of stock prices
        dbc.Col([
            dbc.Stack([
                html.Label(id = 'Msg', children = ' Batch: ',style = {'color':'rgb(0, 128, 0)'}),
                dcc.Dropdown(id='dropvalue',
                    options=[
                             {'label': 'First', 'value': 'First'},
                             {'label': 'Last', 'value': 'Last'}
                    ],
                    value='Last',
                    style = {'textAlign':'center','marginLeft':'5px','width':'97%'},
                    multi=False,
                    clearable=False
                ),
            ], direction='horizontal')
        # xs is the sizing for smaller screens like phones    
        ],width = 3),
    ]),
    
    # lets user update data themselves
    html.Button('Update', id='interval-component',style = {'marginLeft':'10px'}),
    
    # loading button
    dbc.Spinner(children=[html.Div(id='page-1-content')], color = 'rgb(0,128,0)'),
    # html.Div(id='page-1-content')
])

# callback takes in 4 input variables including one to refresh every 15 seconds
@callback(
    Output('page-1-content','children'),
        [
    Input('dropvalue','value'),
    Input('datevalue','value'),
    Input('notional','value'),
    Input('interval-component', 'n_clicks')
        ])

Add a button component to your layout, then pass it as an input to the callback with n_clicks.

Then at the top of the function add in

if n_clicks > 1:
	#do the function to generate your page 1 content
	return page1

One thing to note is with this setup once the user clicks the button it will be >1 so any other changes to dropdowns will auto fire. I prefer to do a different setup when adding a button as the final step for the user before firing the callback.

Instead of the code I showed above use

changed_id = [p['prop_id'] for p in dash.callback_context.triggered][0]
        if 'submit-button' in changed_id: #replace 'submit-button' with the ID of your button
            #do the function
            return page1

This way it’s only looking at the most recent input used in a callback so if you click the button it fires, but then if you change a dropdown it had that dropdown as the most recent prop id in the changed_id so it doesn’t fire again until the user clicks the button again.

Also to note, in the code example you provided I see you already do have a button but you’ve called it interval-component and you’re passing n_clicks so I’m not sure what you’re actually doing on that part? I presume it’s copy pasted code that you didn’t re-name the id but please advise if I’m out to lunch on that and missed something.

Edit: sorry I can’t remember if you do or not, but if you do the second option I supplied I think you also will need to import callback in your dash import, if it doesn’t work add callback to your dash import

from dash import Input, Output, State, dcc, callback

Thanks
Payton