Welcome to Dash Crush - your walk-through tutorial to advanced interactive Data Viz dashboards using Dash Plotly.
In the previous chapter, we created our first Dash app. Now, we will learn some practices to make pretty dashboards.
TL;DR
In this chapter:
- Simple yet effective recipe for a convenient layout
- Using Themes
- Using CSS styles
A beautiful out-of-the box dashboard
Luckily, Dash provides tools that make a dashboard beautiful almost effortlessly. The first is the package dash-bootstrap-components
(dbc
) (https://dash-bootstrap-components.opensource.faculty.ai/) that wraps Bootstrap styles for Dash. DBC provided such components as Navbar, Accordion, or Collapse that make layout designs.
Another nice feature of DBC are themes that enable a quick change of app look. Themes can be explored, viewed here:
https://bootswatch.com/
The second component library is https://www.dash-mantine-components.com/
(dmc
) (https://www.dash-mantine-components.com/) - it wraps React Mantine Library.
It's code time
Let's combine DBC components, themes, and a bit of CSS to implement a layout that splits the screen into a sidebar/work pane/result pane (with navbar and plot pane) to tackle different ways of styling the Dash app.
Install packages
Create and activate your virtual environment and install the following packages:
pip install dash pandas dash-bootstrap-components
Use dash_bootstrap_components' themes
To make the app prettier, we will use the dash_bootstrap_components' MINTY theme (https://bootswatch.com/minty/). To do so, we need to pass it as an external style sheet to the Dash constructor in dashboard.py
:
# dashboard.py
import dash
import dash_bootstrap_components as dbc
from src.layout import layout
EXTERNAL_STYLESHEETS = [
dbc.themes.MINTY
]
APP = dash.Dash(__name__, external_stylesheets=EXTERNAL_STYLESHEETS)
APP.layout = layout.create_layout
# Run the Dash app
if __name__ == "__main__":
APP.run_server(debug=True)
Now, we will create the layout.
The layout
Our layout will be located in the src/layout
folder and split into several files for easier management.
The top-level layout
The definition of a top-level layout is located in the file src/layout/layout.py
. We use DBC's components Container
, Row
, and Col
to split the screen into 3 columns of different widths. In DBC, the full width of a screen is 12 columns. Setting the parameter width=X
of dbc.Col
makes it fill X of 12 columns.
The complete code:
# src/layout/layout.py
import dash_bootstrap_components as dbc
from src.layout.navbar import navbar
from src.layout.sidebar import sidebar_menu
from src.layout.plot_pane import plot_pane
from src.layout.style import COLORS
from src.layout.work_pane import work_pane
def create_layout():
"""Build layout"""
return dbc.Container(
[
dbc.Row(
[
dbc.Col(
[
sidebar_menu(),
],
width=1,
style={"backgroundColor": COLORS["white"]},
),
dbc.Col(
[work_pane()], width=2, style={"backgroundColor": COLORS["light-gray"]}
),
dbc.Col(
[navbar(), plot_pane()],
width=9,
),
]
),
],
fluid=True,
)
In the snippet above, you can also see two ways of styling the components:
style
- dict of parameters to modify properties of the component.className
- point to CSS style. It can be a predefined DBC style or a custom one in theassets
folder.
For better readability, I only defined the top-level structure of the app in src/layout/layout.py
. All the components used are defined in separate files and imported here.
The sidebar
The sidebar will occupy a single left-most column of the screen and contain the menu. In our case, it will be a single button: Plot
.
# src/layout/sidebar.py
"""Sidebar menu"""
from dash import dcc, html
import dash_bootstrap_components as dbc
from src.layout.grid import spacer
def sidebar_menu():
"""Sidebar menu"""
return html.Div(
[
spacer(10),
_buttons(),
],
)
def _buttons():
return html.Div(
[
dbc.Button("Plot", id="plot-button", color="primary"),
],
className="d-grid gap-2",
)
For styling the button, use className="d-grid gap-2"
. d-grid and gap-2
are built-in DBC classes.
The work pane
A work pane is an area where all the parameter manipulation happens. In our case, the work pane will have a width of 2 columns and be located in src/layout/work_pane.py
. It will consist of a label and material selection dropdown.
# src/layout/work_pane.py
from dash import html, dcc
from src.layout.grid import spacer
MATERIALS = ["GaAs", "InAs"]
def work_pane():
"""Work pane"""
return html.Div(
[
spacer(10),
html.Label("Material"),
dcc.Dropdown(
id="material_dropdown",
options=[{"label": name, "value": name} for name in MATERIALS],
value="GaAs",
),
],
)
The result area
The right-most part of the screen will be used for the result presentation. The top part will be the navbar (or title bar), and the bottom will be the plot pane.
The navbar
Let's create a simple navbar that contains only the application's title.
# src/layout/navbar.py
import dash_bootstrap_components as dbc
from dash import html
from src.layout.style import NAVBAR_DISPLAY_STYLE
def navbar():
"""Navbar"""
return html.Div(
html.Nav(
children=[
dbc.Row(
[
dbc.Col(
html.Label(
"Welcome to Band structure visualization dashboard",
style=NAVBAR_DISPLAY_STYLE,
className="text-primary",
),
width={"size": 6, "offset": 3},
),
],
),
]
)
)
For that, use the dbc
's Nav
component and place a single label in the 6 middle columns of it. To do so, set
width={"size": 6, "offset": 3}
We use styling methods:
style
- sets text horizontal centering, font size, and margins.className="text-primary"
- is a built-in CSS class, that sets text color to the primary color of the BBC Theme.
The plot pane
The last functional area is the plot pane containing dcc.Graph
# src/layout/plot_pane.py
from dash import dcc
from dash import html
import plotly.graph_objs as go
def create_empty_figure():
"""Style empty figure"""
layout = go.Layout(autosize=True)
return go.Figure(data=[], layout=layout)
def plot_pane():
"""plot pane"""
return html.Div(
dcc.Graph(
id="band_structure_graph",
figure=create_empty_figure(),
className="dc-graph",
),
)
We style dcc.Graph
with a custom CSS dc-graph
. create_empty_figure
makes an empty graph (before the first plot) look nicer.
Helpers
All the main components of the layout are now in place. However, some pieces of code are not yet defined - style and grid. They are common and reusable for different code blocks.
Spacer
To easily add spacing between components, we can use the wrapper spacer
, located in src/layout/grid.py
# src/layout/grid.py
import dash_bootstrap_components as dbc
def spacer(height):
"""Add empty row"""
return dbc.Row(style={"height": f"{height}px"})
The style
Style definitions are located in src/layout/style.py
# src/layout/style.py
COLORS = {
"white": "white",
"light-gray": "#f4f4f4",
"dark-gray": "#d6d6d6",
}
NAVBAR_DISPLAY_STYLE = {
"display": "inline-block",
"width": "90%",
"textAlign": "center",
"fontSize": "24px",
"paddingTop": "6px",
"height": "50px",
}
Now, we have all the components in place. We have not defined any CSS styles yet, but Dash does not throw an error on missing CSS. Therefore, we can check how our app would look like without styling.
As you can see, columns do not use the whole screen height. To overcome this flaw, we need to use some CSS.
Custom CSS
The custom CSS styles need to be located in the assets
folder for Dash to automatically pick them.
So, let's add the CSS we have used in the code above.
For the sidebar, we use:
dc-graph
class to set no side padding. It is defined inassets/dc-graph.css
/* assets/dc-graph.css */
.dc-graph {
height: calc(100vh - 50px); /* full-visible-height - navbar */
}
In this CSS, we set the plot pane's height to be full height minus the height of the navbar (which was defined as 50 pixels in NAVBAR_DISPLAY_STYLE
). In that way, the dashboard utilizes all of the space available.
Nota bene: this example shows one of the difficulties of using custom CSS. There is no easy way to share variables between CSS and Python code (well, at least I have not encountered one yet). Consequently, we need to define the navbar's height in two places and if one changes - also modify the other.
Callback
To make our dashboard interactive, we need to add a callback, that will plot the graph when the "Plot" button is clicked. We will define it in src/callbacks.py
# src/callbacks.py
from dash import Input, Output, State
from dash.exceptions import PreventUpdate
import pandas as pd
import plotly.graph_objs as go
MATERIAL_DATA = {"GaAs": pd.read_csv("GaAs.dat"), "InAs": pd.read_csv("InAs.dat")}
TRACES = ["E_so", "E_lh", "E_hh", "E_c"]
def create_figure(to_draw):
"""Style graph"""
layout = go.Layout(
autosize=True,
title="Band structure",
xaxis={"title": "k (wave vector) [1/nm]"},
yaxis={"title": "E (Energy) [eV]"},
)
return go.Figure(data=to_draw, layout=layout)
def create_callbacks(app):
"""Create callbacks"""
@app.callback(
Output("band_structure_graph", "figure"),
Input("plot_button", "n_clicks"),
State("material_dropdown", "value"),
prevent_initial_call=True,
)
def plot(n_clicks, material): # pylint: disable=unused-argument
if not n_clicks or not material:
raise PreventUpdate
data = MATERIAL_DATA[material]
to_draw = []
for trace in TRACES:
to_draw.append(go.Scatter(x=data["k"], y=data[trace], mode="lines", name=trace))
return create_figure(to_draw)
This callback draws the Band Structure of a selected material (Gallium Arsenide or Indium Arsenide) every time the "Plot" button is clicked. The chosen material is passed to the callback as State
. On click, the Scatter trace for every energy profile is added to the plot data. Later, the figure's layout is defined, and the figure is returned.
If you need a more detailed explanation of this callback, please refer to the previous part of Dash Crush, as the only difference in the code is the Input trigger being the "Plot" button click instead of a dropdown selection change.
(REMARK: In the older version of Dash, it was required to pass a list of Outputs, a list of Inputs, and a list of States. For some releases it is no longer required, but still supported.)
The final step is importing create_callbacks
in dashboard.py
:
from src.callbacks import create_callbacks
and calling it there:
create_callbacks(APP)
Starting the app:
Start the app with python dashboard.py
. You should see an indication of a running app in your console. The dashboard is available under http://localhost:8050.
Summary:
Quick recap:
- Use
dbc.Themes
for a beautiful, out-of-the-box appearance. - Splitting the screen into a sidebar/work pane/result pane yields good UX.
- Use custom CSS to enhance your app's look.
- Place custom CSS in
assets
for automatic pick-up.
The complete code from this chapter can be found on GitHub:
https://github.com/ptrhbt/dash-crush/tree/1-layout
There is also a file requirements.txt
which lists all the packages used. Just install it to your virtual env with pip install -r requirements.txt
.
We will explore more of Dash magic in the next parts of Dash Crush.