Create a Multipage Dash Application
By: Joél Collins
Tags: App Development, Data, Dashboard, Python, Dash

Making a dashboard is a great way to communicate the value of data science projects to coders and non-coders alike!

For those of us that work in data science, often times our skills with data come with a lack of skill in front end development. Dash, a library built by Plotly, offers simple boiler plate code for developing interactive web applications in Python.

For inspiration, check out the Dash Gallery!

In this article, I walk through the steps of building a basic, multi-page, dash application. The code and data for this project can be found at this Github repo.

Why a multipage app?

Learning to build a multi-page app is useful for a number of reasons. Here are a few:

Allows adding a separate page for methodology or background information. Breaking up a complex data project between multiple pages can make for a smooth user experience. Can be used to build an online portfolio! Understanding the workflow can serve as a gentle baby step into the world of web development.

Application structure:

Project Directory:

index.py is ultimately the file the will run the entire application. Consider it like the table of contents for the project. Each page will be built in separate files and then imported into index.py. This workflow is used to keep files concise and readable, and to keep the structure of the project organized and flexible.

D.R.Y Code

This workflow helps avoid repetitive code. For instance, each page of this application requires a navigation bar. Instead of writing the same code in every file, the component is simply imported.

Create Directory and Environment

First we create a folder to hold all of our project files:

NOTE: I personally prefer to work in either Visual Studio Code or Pycharm, but for the sake of keeping this walkthrough accessible we will stick to building this application in terminal.

In terminal:

mkdir dash_app

cd dash_app

Next thing we do is create a virtual environment to build the application. No, this is not technically a requirement, but it removes the risk of having dependency errors, and generally is best practice when building web applications.

pip install virtualenv

virtualenv venv

source venv/bin/activate

(venv) should now appear before the $ sign in your terminal. This means that you are now using your virtual environment.

With the virtual environment activated, we need to install the libraries we will be using for this project.

pip install pandas

pip install dash

pip install dash-bootstrap-components ### These are dependences and are likely already installed pip install dash-core-components

pip install dash-html-components

pip install plotly

Building the Navbar:

NOTE: The code for the navbar and the rest of the homepage was copied directly from the dash bootstrap components website.

Dash bootstrap components divides the webpage into a grid, and is a useful tool for organizing the layout of a web application. For more information on positioning app components (anything that appears on the webpage) follow this link and examine the use of dbc.Row and dbc.Column.

Open vim text editor in terminal.

vim navbar.py

s

s enables edit mode so we can begin writing our code!

In navbar.py:

import dash_bootstrap_components as dbc def Navbar():

navbar = dbc.NavbarSimple(

children=[

dbc.NavItem(dbc.NavLink("Time-Series", href="/time-series")),

dbc.DropdownMenu(

nav=True,

in_navbar=True,

label="Menu",

children=[

dbc.DropdownMenuItem("Entry 1"),

dbc.DropdownMenuItem("Entry 2"),

dbc.DropdownMenuItem(divider=True),

dbc.DropdownMenuItem("Entry 3"),

],

),

],

brand="Home",

brand_href="/home",

sticky="top",

) return navbar

In the sixth line above, the href parameter tells the application which page to return to the user (if you place https:// at the beginning you can navigate to outside websites!)

In this line of code, we set the href to “/time-series”. This will be the name of the link for our time-series graph.

Save and exit the file.

Esc

:wq [ENTER]

Homepage:

vim homepage.py

## s to enable edit mode

In homepage.py first we need to import the libraries needed.

import dash

import dash_bootstrap_components as dbc

import dash_core_components as dcc

import dash_html_components as html

We also want to import the Navbar function from navbar.py and create a Navbar object.

from navbar import Navbar nav = Navbar()

Build the homepage body:

The layout of this page will be a grid of 2 grid spaces. 1 rows, 2 columns.

Column 1:

Header

Paragraph

Button

Column 2:

Header

Graph

Extra credit: Change the graph to an image.

body = dbc.Container(

[

dbc.Row(

[

dbc.Col(

[

html.H2("Heading"),

html.P(

"""\

Donec id elit non mi porta gravida at eget metus.Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentumnibh, ut fermentum massa justo sit amet risus. Etiam porta semmalesuada magna mollis euismod. Donec sed odio dui. Donec id elit nonmi porta gravida at eget metus. Fusce dapibus, tellus ac cursuscommodo, tortor mauris condimentum nibh, ut fermentum massa justo sitamet risus. Etiam porta sem malesuada magna mollis euismod. Donec sedodio dui."""

),

dbc.Button("View details", color="secondary"),

],

md=4,

),

dbc.Col(

[

html.H2("Graph"),

dcc.Graph(

figure={"data": [{"x": [1, 2, 3], "y": [1, 4, 9]}]}

),

]

),

]

)

],

className="mt-4",

)

The next bit of code is important.

Because we are building a multipage application, we need to be able to import the layout into other files. To do this, we will build a Homepage function that returns the entire layout for the page.

Note: Layouts must always be a dash html component. The standard is to wrap the layout inside a div.

def Homepage():

layout = html.Div([

nav,

body

]) return layout

Before moving on, let’s make sure the application is working properly. To do this, add the following code to the bottom of the page.

app = dash.Dash(__name__, external_stylesheets = [dbc.themes.UNITED]) app.layout = Homepage() if __name__ == "__main__":

app.run_server()

Save and exit vim.

Esc

:wq [ENTER]

Great! Now let’s run the application to make sure the code works!

python homepage.py

If the application fails, an error message will print out. Get used to this, it is very much the work flow of application building. For me, 90% of the time it is a simple syntax error. Read the error message and debug the problem. (If there is an issue with my code, let me know in the comments!)

If the application runs, several lines will print out. The important line will look something like this:

Copy the http url and paste it into an internet browser.

Presto! An application is born!

After you’ve admired your creation for a suitable amount of time, shut down the web app.

[CTR] C

##### It will look like this in terminal: ^C

App

Now let’s build our interactive graph page!

First we create an app.py file.

vim app.py

## s to enable edit mode

Next, import the libraries for the application, our Navbar function, and also the Illinois population data.

### Data

import pandas as pd

import pickle ### Graphing

import plotly.graph_objects as go ### Dash

import dash

import dash_core_components as dcc

import dash_html_components as html

import dash_bootstrap_components as dbc

from dash.dependencies import Output, Input

## Navbar

from navbar import Navbar df = pd.read_csv(' https://gist.githubusercontent.com/joelsewhere/f75da35d9e0c7ed71e5a93c10c52358d/raw/d8534e2f25495cc1de3cd604f952e8cbc0cc3d96/population_il_cities.csv ') df.set_index(df.iloc[:,0], drop = True, inplace = True) df = df.iloc[:,1:]

The last two lines of code are simply setting the date column as the index and then removing a duplicate of the column from the dataset.

Ok now let’s build some components!

Navbar:

nav = Navbar()

Header:

We will use the header to provide some instruction to user.

header = html.H3(

'Select the name of an Illinois city to see its population!'

)

Dropdown:

For this application we will be building a dropdown menu. In the background, dropdown menus are formatted as a list of dictionaries. This dictionary has two keys. “label” and “value”. label = what the user will see in the dropdown menu, and value = what you are returning to the application to query the data.

Since each column is an individual city in Illinois, we will be using the column names of this dataset for querying.

Every column is formatted as “City, Illinois”. Having the “, Illinois” included for every label is a bit overkill since we are only looking cities from Illinois. In the code below we will remove this extra bit of text for the label of each dictionary:

options = [{'label':x.replace(', Illinois', ''), 'value': x} for x in df.columns]

Now, we plug the list of dictionaries directly into a dropdown object

IMPORTANT: Any component that is interactive must have an id name. You’ll see why soon. Additionally, components cannot share id-names. If an id is used more than once, an error will be thrown and the application will break.

The parameters in the dcc.Dropdown function are.

id: A unique identifier for a component. Formatted as a string.

A unique identifier for a component. Formatted as a string. options: The list of dictionaries with “label” and “value” keys

The list of dictionaries with “label” and “value” keys value: A default value that the dropdown is set to when the application loads. (I chose a random city from the dataset for this default.)

dropdown = html.Div(dcc.Dropdown(

id = 'pop_dropdown',

options = options,

value = 'Abingdon city, Illinois'

))

Dropdown complete!

Output space:

This is where we will output our graph.

output = html.Div(id = 'output',

children = [],

)

That’s all of the components!

Now, exactly like we did for our homepage, we will make an layout function called “App”.

def App():

layout = html.Div([

nav,

header,

dropdown,

output

]) return layout

Alright, now let’s make our graph. We’ll create a build_graph function that will accept a city from the dropdown menu and return a Plotly graph showing that city’s population trend.

def build_graph(city): data = [go.Scatter(x = df.index,

y = df[city],

marker = {'color': 'orange'})] graph = dcc.Graph(

figure = {

'data': data, 'layout': go.Layout(

title = '{} Population Change'.format(city),

yaxis = {'title': 'Population'},

hovermode = 'closest'

)

}

) return graph

Ok! That’s it for this file. Make sure to save and exit.

Esc

:wq [ENTER]

Index

Now let’s bring it all together with an index.py file!

vim index.py

## s to enable edit mode

Import all of the libraries and functions.

import dash

import dash_core_components as dcc

import dash_html_components as html

from dash.dependencies import Input, Output

import dash_bootstrap_components as dbc from app import App, build_graph from homepage import Homepage

Next, we declare a dash app object. To make things nice and pretty we will use a theme by passing in a dbc.themes external stylesheet. The name of the theme is always upper case and is wrapped inside a list. Feel free to peruse the available themes here.

app = dash.Dash(__name__, external_stylesheets=[dbc.themes.UNITED])

IMPORTANT: In order to make our time series graph interactive, we have to create a callback function for the dropdown menu and output space. However, dash doesn’t allow callbacks for components that don’t exist in the layout. Because there is no dropdown menu or output space in the homepage layout, we must change the configurations of our app.

app.config.suppress_callback_exceptions = True

Next we create a dcc.Location object that is not visible in the application, but monitors the url. This will tell our application to return a page based on what link the user clicks.

Now, create a content space where we return either the homepage or our time-series page.

app.layout = html.Div([

dcc.Location(id = 'url', refresh = False),

html.Div(id = 'page-content')

])

Callbacks

Alright! Now let’s make our app interactive so things actually happen when the user clicks on stuff.

We will need two callbacks. One to return different pages of the app, and another to update the graph.

In order to do this, we declare @app.callback

Inside app.callback we pass in the Output function we imported at the top of the file. The parameters for Output are:

The id for the component that is being updated. The parameter of the component that is being updated. (90% of the time, you will be updating a div and this parameter will be set to “children”)

Note: A callback can update multiple output spaces. Simply wrap all of the Output functions inside a list.

Next we pass the Input function into app.callback.

**Input is always wrapped inside a list. Even if there is only one input**

The parameters for Input are the same as Output

In this case we are outputting to our ‘page-content’ div, and inputting from our ‘url’ location.



[Input('url', 'pathname')])

def display_page(pathname):

if pathname == '/time-series':

return App() @app .callback(Output('page-content', 'children'),[Input('url', 'pathname')])def display_page(pathname):if pathname == '/time-series':return App() else:

return Homepage()

Now we create the callback for our time-series graph.

Because we made a build_graph function, this callback is super simple.



Output('output', 'children'),

[Input('pop_dropdown', 'value')]

)

def update_graph(city): @app .callback(Output('output', 'children'),[Input('pop_dropdown', 'value')]def update_graph(city): graph = build_graph(city) return graph

Lastly, to make the application run.

if __name__ == '__main__':

app.run_server(debug=True)

Notice I have set debug=True. When deploying your app, you would set this to false, but we are in the development phase, and setting debug to True makes debugging our application a bit easier.

Save and exit:

Esc

:wq [ENTER]

And run the app!

python index.py

Conclusion

This is a simple example, but these fundamentals can be used to build much more complex and powerful applications!

Drop a comment and let me know if you have any questions.

P.S. To exit your virtual environment.