Introduction to Plotly Dash

In this module, we will learn how to build interactive dashboards using Plotly Dash. We will cover the basics of creating a Dash application, including how to set up the layout and callbacks to create interactive components. We will also explore some of the advanced features of Dash, such as integrating with other libraries and deploying our applications. After completing this module, students should be able to:

  • Create a basic Dash application with interactive components

  • Customize the layout and styling of their Dash applications

  • Use callbacks to create dynamic and responsive dashboards

  • Integrate Dash with other libraries and tools for data visualization and analysis

  • Deploy their Dash applications to the web for sharing and collaboration

What is Plotly Dash?

Plotly Dash is a Python framework for building interactive web applications and dashboards. The tool was developed by the company Plotly, which is known for its powerful data visualization libraries, and released in 2017. It is built on top of the Plotly.js library for data visualization and the React.js library for building user interfaces. Dash allows developers to create complex and interactive applications using only Python, without needing to write any JavaScript, CSS, or HTML. Dash applications can be easily deployed to the web, making it a powerful tool for sharing data visualizations and insights with others for data-driven analysis.

Getting Started with Dash

Installation

To get started, let’s install Dash in our virtual environment on our Linux VM. You can do this using pip:

[mbs337-vm]$ cd $HOME/mbs-337
[mbs337-vm]$ source .venv/bin/activate
(.venv) [mbs337-vm]$ pip3 install dash
(.venv) [mbs337-vm]$ pip3 list
Package                   Version
------------------------- -----------
annotated-types           0.7.0
anyio                     4.12.1
argon2-cffi               25.1.0
argon2-cffi-bindings      25.1.0
arrow                     1.4.0
asttokens                 3.0.1
async-lru                 2.2.0
attrs                     25.4.0
babel                     2.18.0
beautifulsoup4            4.14.3
biopython                 1.86
bleach                    6.3.0
blinker                   1.9.0
cattrs                    26.1.0
certifi                   2026.1.4
cffi                      2.0.0
charset-normalizer        3.4.4
click                     8.3.1
comm                      0.2.3
contourpy                 1.3.3
cryptography              46.0.5
cycler                    0.12.1
dash                      4.0.0
debugpy                   1.8.20
decorator                 5.2.1
defusedxml                0.7.1
executing                 2.2.1
fastjsonschema            2.21.2
Flask                     3.1.3
fonttools                 4.61.1
fqdn                      1.5.1
graphql-core              3.2.7
h11                       0.16.0
httpcore                  1.0.9
httpx                     0.28.1
idna                      3.11
importlib_metadata        8.7.1
iniconfig                 2.3.0
ipykernel                 7.2.0
ipython                   9.10.0
ipython_pygments_lexers   1.1.1
ipywidgets                8.1.8
isoduration               20.11.0
itsdangerous              2.2.0
jaraco.classes            3.4.0
jaraco.context            6.1.0
jaraco.functools          4.4.0
jedi                      0.19.2
jeepney                   0.9.0
Jinja2                    3.1.6
json5                     0.13.0
jsonpointer               3.0.0
jsonschema                4.26.0
jsonschema-specifications 2025.9.1
jupyter                   1.1.1
jupyter_client            8.8.0
jupyter-console           6.6.3
jupyter_core              5.9.1
jupyter-events            0.12.0
jupyter-lsp               2.3.0
jupyter_server            2.17.0
jupyter_server_terminals  0.5.4
jupyterlab                4.5.5
jupyterlab_pygments       0.3.0
jupyterlab_server         2.28.0
jupyterlab_widgets        3.0.16
keyring                   25.7.0
kiwisolver                1.4.9
lark                      1.3.1
lxml                      6.0.2
markdown-it-py            4.0.0
MarkupSafe                3.0.3
matplotlib                3.10.8
matplotlib-inline         0.2.1
mdurl                     0.1.2
mistune                   3.2.0
more-itertools            10.8.0
narwhals                  2.17.0
nbclient                  0.10.4
nbconvert                 7.17.0
nbformat                  5.10.4
nest-asyncio              1.6.0
notebook                  7.5.4
notebook_shim             0.2.4
numpy                     2.4.1
packaging                 26.0
pandas                    3.0.1
pandocfilters             1.5.1
parso                     0.8.6
pexpect                   4.9.0
pillow                    12.1.1
pip                       26.0.1
platformdirs              4.9.2
plotly                    6.6.0
pluggy                    1.6.0
plumbum                   1.10.0
ply                       3.11
prometheus_client         0.24.1
prompt_toolkit            3.0.52
psutil                    7.2.2
ptyprocess                0.7.0
pure_eval                 0.2.3
pycparser                 3.0
pydantic                  2.12.5
pydantic_core             2.41.5
Pygments                  2.19.2
pyinaturalist             0.21.1
pyparsing                 3.3.2
pyrate-limiter            2.10.0
pytest                    9.0.2
python-dateutil           2.9.0.post0
python-json-logger        4.0.0
PyYAML                    6.0.3
pyzmq                     27.1.0
rcsb-api                  1.5.0
redis                     7.2.0
referencing               0.37.0
requests                  2.32.5
requests-cache            1.3.0
requests-ratelimiter      0.8.0
retrying                  1.4.2
rfc3339-validator         0.1.4
rfc3986-validator         0.1.1
rfc3987-syntax            1.1.0
rich                      14.3.3
rpds-py                   0.30.0
rustworkx                 0.17.1
seaborn                   0.13.2
SecretStorage             3.5.0
Send2Trash                2.1.0
setuptools                82.0.0
six                       1.17.0
soupsieve                 2.8.3
stack-data                0.6.3
terminado                 0.18.1
tinycss2                  1.4.0
tornado                   6.5.4
tqdm                      4.67.3
traitlets                 5.14.3
typing_extensions         4.15.0
typing-inspection         0.4.2
tzdata                    2025.3
uri-template              1.3.0
url-normalize             2.2.1
urllib3                   2.6.3
wcwidth                   0.6.0
webcolors                 25.10.0
webencodings              0.5.1
websocket-client          1.9.0
Werkzeug                  3.1.6
widgetsnbextension        4.0.15
zipp                      3.23.0

Notice that not only was Dash installed, but also its dependencies, including Flask and Plotly. Flask is a lightweight web framework that Dash uses to serve the application, while Plotly is the library that provides the data visualization capabilities for Dash applications.

Our First Dash Application

To start, we’re going to create the simplest possible Dash application, the infamous “Hello, Dash!” app. First, let’s create a new directory called dash_app in our mbs-337 directory on the Linux VM, and then create a new Python file called app.py inside that directory:

(.venv) [mbs337-vm]$ mkdir dash_app
(.venv) [mbs337-vm]$ cd dash_app
(.venv) [mbs337-vm]$ touch app.py
(.venv) [mbs337-vm]$ ls -l
total 0
-rw-rw-r-- 1 ubuntu ubuntu 0 Mar  8 18:45 app.py

Next, let’s open the app.py file in our VS Code editor and add the following code:

1from dash import Dash, html
2
3app = Dash()
4
5app.layout = [html.Div(children='Hello, Dash!')]
6
7if __name__ == '__main__':
8    app.run(host='0.0.0.0', port=8050, debug=True)

This code creates a simple Dash application that displays the text “Hello, Dash!” on the page. The important lines to note are line 3, where we initialize the Dash application, line 5, where we define the layout of the application, and line 8, where we run the application. The application is set to run on all available network interfaces (0.0.0.0) on port 8050, which allows it to be accessed from other devices on the same network. The debug=True argument is used to enable debug mode, which provides helpful error messages and automatically reloads the application when changes are made to the code. This is useful during development, but should be set to False in production environments for security reasons.

Note

What are Network Ports?

A network port is a communication endpoint that allows different applications and services to communicate with each other over a network. Each port is identified by a unique number, which ranges from 0 to 65535. Ports below 1024 are considered well-known ports and are typically reserved for system or well-known services and require superuser/root privileges to bind to. Ports are used to route network traffic to the correct application or service on a server. For example, HTTP traffic typically uses port 80, while HTTPS traffic uses port 443. In our Dash application, we are using port 8050, which is commonly used for development servers and is not reserved for any specific service.

Commonly used ports include:
  • 21: FTP (File Transfer Protocol) Data Transfer

  • 22: SSH (Secure Shell)

  • 80: HTTP (Hypertext Transfer Protocol)

  • 443: HTTPS (HTTP Secure)

  • 6379: Redis Database

  • 8888: Jupyter Notebook Server

To run the application, we can use the following command in our VS Code terminal:

(.venv) [mbs337-vm]$ curl ip.me
129.114.38.51
(.venv) [mbs337-vm]$ python app.py
Dash is running on http://0.0.0.0:8050/

 * Serving Flask app 'app'
 * Debug mode: on

Now, we can open a web browser and navigate to http://<IP_ADDRESS>:8050/ (replacing <IP_ADDRESS> with the actual IP address of your Linux VM) to see our Dash application in action. You should see a page that says “Hello, Dash!” displayed in the upper left.

../_images/dash-first-app.png

“Hello, Dash!” application running in a web browser.

Adding Data and a Basic Table

Most dashboards are used to display data, so let’s add some data to our Dash application. We will use the pandas library to create a simple DataFrame and then display it in our Dash application using the dash-ag-grid component. We will pull in the same Austin Animal Center Outcomes dataset that we used in Unit 7 for our Exploratory Data Analysis. In our VS Code terminal, we can use the following command to download the dataset:

(.venv) [mbs337-vm]$ wget https://raw.githubusercontent.com/tacc/mbs-337-sp26/main/docs/unit07/sample-data/Austin_Animal_Center_Outcomes.zip
(.venv) [mbs337-vm]$ unzip Austin_Animal_Center_Outcomes.zip
Archive:  Austin_Animal_Center_Outcomes.zip
  inflating: Austin_Animal_Center_Outcomes.csv
(.venv) [mbs337-vm]$ rm Austin_Animal_Center_Outcomes.zip
(.venv) [mbs337-vm]$ ls -l
total 20844
-rw-rw-r-- 1 ubuntu ubuntu 21338783 May  7  2025 Austin_Animal_Center_Outcomes.csv
-rw-rw-r-- 1 ubuntu ubuntu      171 Mar  8 19:18 app.py

We also need to install the dash-ag-grid component, which we can do using pip:

(.venv) [mbs337-vm]$ pip3 install dash-ag-grid
(.venv) [mbs337-vm]$ pip3 list | grep dash_ag_grid
dash_ag_grid              33.3.3

Now, we can modify our app.py file to read in the dataset with pandas into a DataFrame and display it in a table. We will use the dash_ag_grid component to create an interactive table that allows us to sort and filter the data. Here is the modified code for our Dash application:

 1import dash_ag_grid as dag
 2import pandas as pd
 3from dash import Dash, html
 4
 5df = pd.read_csv('Austin_Animal_Center_Outcomes.csv')
 6
 7app = Dash()
 8
 9app.layout = [
10    html.Div(children='Austin Animal Center Outcomes'),
11    dag.AgGrid(
12        rowData=df.to_dict('records'),
13        columnDefs=[{'field': col} for col in df.columns]
14    )
15]
16
17if __name__ == '__main__':
18    app.run(host='0.0.0.0', port=8050, debug=True)

Again, to run the application, we can use the following command in our VS Code terminal:

(.venv) [mbs337-vm]$ python app.py
Dash is running on http://0.0.0.0:8050/

 * Serving Flask app 'app'
 * Debug mode: on

And then we can refresh our web browser to see the updated application with the data table displayed.

../_images/dash-app-with-data-table.png

Dash app with data table running in a web browser.

Adding a Visualization

To add some more visual flair to our dashboard, let’s add a visualization using the Plotly library. We will have to import the common components from Dash (dcc), as well as the Plotly Express library (plotly.express) to get access to the plotting functions. We can create a simple histogram to show the distribution of outcomes in the dataset. We will use the dcc.Graph component from Dash to display the Plotly graph (px.histogram) in our application. Here is the modified code for our Dash application with the histogram added:

 1import dash_ag_grid as dag
 2import pandas as pd
 3import plotly.express as px
 4from dash import Dash, html, dcc
 5
 6df = pd.read_csv('Austin_Animal_Center_Outcomes.csv')
 7
 8app = Dash()
 9
10app.layout = [
11    html.Div(children='Austin Animal Center Outcomes'),
12    dag.AgGrid(
13        rowData=df.to_dict('records'),
14        columnDefs=[{"field": col} for col in df.columns]
15    ),
16    dcc.Graph(figure=px.histogram(df, x='Outcome Type', title='Distribution of Outcome Types')
17    )
18]
19
20if __name__ == '__main__':
21    app.run(host='0.0.0.0', port=8050, debug=True)

Hopefully, our Dash app is still running in our terminal, but if not, we can restart it with the same command as before (python app.py). After refreshing our web browser, we should now see a histogram displayed below the data table that shows the distribution of outcome types in the dataset.

../_images/dash-app-with-data-and-histogram.png

Dash app with data table and histogram running in a web browser.

Adding Interactivity with Callbacks

Our application is starting to look like a real dashboard, but it is still not very interactive. To add some interactivity, we can use Dash’s callback system to create dynamic components that respond to user input. For example, we can create some controls that allow us to pick what column to use for the histogram, and then update the graph based on the user’s selection. We will achieve this by adding a set of radio buttons to the layout that allow the user to select a column from the dataset, and then we will use a callback function to update the histogram based on the selected column. We will have to import the callback decorator from Dash to define our callback function and the Input and Output classes to specify the inputs and outputs of the callback. Our dash import statement will now look like this:

from dash import Dash, Input, Output, callback, dcc, html

Now we need to update our layout to include the radio buttons for selecting the column to use for the histogram and give it an id so that we can reference it in our callback function. We will also give the dcc.Graph component an id so that we can update it in the callback. Here is the modified layout:

app.layout = [
    html.Div(children='Austin Animal Center Outcomes'),
    html.Hr(),
    dcc.RadioItems(options=['Outcome Type', 'Outcome Subtype', 'Animal Type'], value='Outcome Type', id='radio-items'),
    dag.AgGrid(
        rowData=df.to_dict('records'),
        columnDefs=[{"field": col} for col in df.columns]
    ),
    dcc.Graph(figure={}, id='outcome-graph')
]

Next, we need to define our callback function that will update the histogram based on the selected column. We will use the @callback decorator to specify that this function is a callback, and we will use the Input and Output classes to specify that the input to the callback is the value of the radio buttons and the output is the figure of the graph. Here is the code for our callback function:

@callback(
    Output('outcome-graph', 'figure'),
    Input('radio-items', 'value')
)
def update_graph(value):
    fig = px.histogram(df, x=value, title=f'Distribution of {value}')
    return fig

Finally, putting it all together, here is the complete code for our Dash application with the interactive histogram:

 1import dash_ag_grid as dag
 2import pandas as pd
 3import plotly.express as px
 4from dash import Dash, Input, Output, callback, dcc, html
 5
 6df = pd.read_csv('Austin_Animal_Center_Outcomes.csv')
 7
 8app = Dash()
 9
10app.layout = [
11    html.Div(children='Austin Animal Center Outcomes'),
12    html.Hr(),
13    dcc.RadioItems(options=['Outcome Type', 'Outcome Subtype', 'Animal Type'], value='Outcome Type', id='radio-items'),
14    dag.AgGrid(
15        rowData=df.to_dict('records'),
16        columnDefs=[{"field": col} for col in df.columns]
17    ),
18    dcc.Graph(figure={}, id='outcome-graph')
19]
20
21@callback(
22    Output('outcome-graph', 'figure'),
23    Input('radio-items', 'value')
24)
25def update_graph(value):
26    fig = px.histogram(df, x=value, title=f'Distribution of {value}')
27    return fig
28
29if __name__ == '__main__':
30    app.run(host='0.0.0.0', port=8050, debug=True)

Now, when we run our Dash application and refresh the web browser, we should see the radio buttons above the data table. When we select a different option from the radio buttons, the histogram should update to show the distribution of the selected column. This is the power of Dash’s callback system, which allows us to quickly and easily add interactivity to our dashboards.

../_images/dash-app-with-interactive-histogram.png

Dash app with data table and interactive histogram running in a web browser.

Styling and Customization

Our application is functional, but it could use some styling and customization to make it look nicer. Dash provides a lot of options for styling and customizing the appearance of our applications, including built-in themes, CSS styling, the Dash Design Kit (Dash Enterprise only), Dash Bootstrap and Mantine components, and the ability to create custom components. We will use the dash-bootstrap-components library to add some Bootstrap styling to our application. Dash Bootstrap Components is a library built off of the popular Bootstrap framework that can be easily integrated into Dash applications to provide a clean and responsive design. We can install it using pip:

(.venv) [mbs337-vm]$ pip3 install dash-bootstrap-components
(.venv) [mbs337-vm]$ pip3 list | grep dash_bootstrap_components
dash-bootstrap-components 2.0.4

Now we can modify our Dash application to use some Bootstrap components and styling. We will import the dash_bootstrap_components library.

import dash_bootstrap_components as dbc

First, we will add a Bootstrap theme called “DARKLY” to our application by passing the URL for the theme CSS file to the external_stylesheets argument when initializing the Dash app.

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

Then we can update our layout to use some Bootstrap components, such as the dbc.Container and dbc.Row components to create a responsive layout for our dashboard.

app.layout = dbc.Container([
    dbc.Row([
        html.Div("Austin Animal Center Outcomes", className="text-primary text-center fs-3")
    ]),
    dbc.Row([
        dbc.RadioItems(options=['Outcome Type', 'Outcome Subtype', 'Animal Type'],
                    value='Outcome Type', id='radio-items', inline=True)
    ]),
    dbc.Row([
        dag.AgGrid(
            rowData=df.to_dict('records'),
            columnDefs=[{"field": col} for col in df.columns]
        )
    ]),
    dbc.Row([
        dcc.Graph(figure={}, id='outcome-graph')
    ])
], fluid=True)

Finally, putting it all together, here is the complete code for our Dash application with Bootstrap styling:

 1import dash_ag_grid as dag
 2import dash_bootstrap_components as dbc
 3import pandas as pd
 4import plotly.express as px
 5from dash import Dash, Input, Output, callback, dcc, html
 6
 7df = pd.read_csv('Austin_Animal_Center_Outcomes.csv')
 8
 9external_stylesheets = [dbc.themes.DARKLY]
10app = Dash(__name__, external_stylesheets=external_stylesheets)
11
12app.layout = dbc.Container([
13    dbc.Row([
14        html.Div("Austin Animal Center Outcomes", className="text-primary text-center fs-3")
15    ]),
16    dbc.Row([
17        dbc.RadioItems(options=['Outcome Type', 'Outcome Subtype', 'Animal Type'],
18                    value='Outcome Type', id='radio-items', inline=True)
19    ]),
20    dbc.Row([
21        dag.AgGrid(
22            rowData=df.to_dict('records'),
23            columnDefs=[{"field": col} for col in df.columns]
24        )
25    ]),
26    dbc.Row([
27        dcc.Graph(figure={}, id='outcome-graph')
28    ])
29], fluid=True)
30
31@callback(
32    Output('outcome-graph', 'figure'),
33    Input('radio-items', 'value')
34)
35def update_graph(value):
36    fig = px.histogram(df, x=value, title=f'Distribution of {value}')
37    return fig
38
39if __name__ == '__main__':
40    app.run(host='0.0.0.0', port=8050, debug=True)

Now, when we run our Dash application and refresh the web browser, we should see a nicely styled dashboard with a dark theme, a centered title, and inline radio buttons for selecting the column to use for the histogram. The data table and histogram should still be functional and interactive as before.

../_images/dash-app-with-styling.png

Dash app with styling and customization running in a web browser.

Deploying Dash Applications

In essence, our Dash application is already “deployed” in the sense that it is running on our Linux VM and can be accessed publicly (because the VM is configured to allow incoming connections on port 8050).

If you don’t have access to a server or VM to deploy your Dash application, there are several options for deploying Dash applications, including using Dash Enterprise (a commercial platform for deploying Dash apps), Plotly Cloud (a cloud hosting service for Plotly and Dash applications), or general purpose hosting services like Heroku, AWS, or PythonAnywhere.

Additional Resources