Dashboards in Python for Beginners and Everyone Else using Dash

Eric Kleppen
Jan 7, 2020 · 14 min read
Image for post
Image for post

I love sharing and visualizing data as much as analyzing it, so I was enthusiastic about a project at work that gave me an excuse to finally learn Dash. Dash is a framework for Python written on top of Flask, Plotly.js, and React.js, and it abstracts away the complexities of each of those technologies into easy to apply components. Anyone who has a little Python and HTML experience will feel like Dash empowers them to create customized and interactive web-based dashboards without breaking a sweat. To quote the documentation’s Introduction,

“ Dash is simple enough that you can bind a user interface around your Python code in an afternoon.”

If you want to build something cool without being bogged down by the nuts and bolts under the hood, this is the guide for you. If you’re new to Dash, read on friend!

If you’re already familiar with Dash and want to see the final layout, scroll to the bottom or find the code on my GitHub.

Bookmark my other Dash Tutorials for advanced concepts, more examples, and tips and tricks not found in documentation!

3 Advanced Examples for Dash Beginners
Live Updates and Streaming Data into a Dashboard
Responsive Mobile Dashboards with Bootstrap CSS Components
Creating a Data Table using Data from Reddit
Exporting Data from a Dashboard
How to Setup User Authentication for Dash Apps using Python and Flask

Dashboards Made Simple

After wrapping my head around the core concepts, creating a simple dashboard felt like a “plug and play” experience. Dash can utilize Bootstrap CSS too, which makes styling and page-layout even easier to piece together.

Although I like Dash so far, it uses a lot of dictionaries and lists making it easy to lose your place. Once you get the hang of the patterns, it doesn’t feel bad. It can feel tricky to keep track of where your lists and dictionaries end at the beginning, so my advice is to use a lot of comments to keep track of your place.

I wont cover hosting the application in this article, but I will show you how to create an app and organize into multiple files, making it easier to manage and enhance. Dash and Plotly are vast and powerful tools, and this is only a taste of what they can do. If you really want to impress your friends, dig into the documentation and push yourself to design something amazing. Now onto some code!

Necessary Installations

Run these pip install commands to make sure you have the correct libraries:

pip install pandas
pip install dash
pip install dash-bootstrap-components
pip install plotly

The Wine Data Dashboard

I am using the wine review dataset from Kaggle. I have already cleaned the data and saved it to a sqlite3 database. I cover the data transformations in a previous article. This is the basic dashboard I’ll show how to create. We will create an app that has two tabs and two filters:

Image for post
Image for post
Image for post
Image for post

The first tab is the raw data in a filterable and sort-able data table.
Tab two is a responsive scatter plot of the data points by price and rating.
The data can be filtered using the filters in the side panel.

Image for post
Image for post

Dash Concepts to Know

Before we get into dash-boarding, let me explain a couple things…If you’re already familiar with Dash and just want my layout template, skip this part… Dash apps are primarily composed of two parts:

Layout
Callbacks

Layout

The layout is made up of a tree of components that describe what the application looks like and how users experience the content. If you’re skilled with JavaScript and React.js, you can build your own components; however, Dash ships with multiple component libraries like dash_core_components, dash_html_components, and Dash DataTable. The dash_html_components library has a component for nearly every HTML tag. The dash_core_components library includes higher-level interactive components like buttons, input fields, and dropdowns. Dash DataTable makes it easy to integrate interactive data tables into your application.

Callbacks

Callbacks are what hold the logic for making Dash apps interactive. This will be covered in much more detail when I show you the code, but for now, just understand that Callbacks are Python functions that are automatically called whenever an input component’s property changes. It is possible to chain callbacks, making one change trigger multiple updates throughout the app.

Callbacks are made up of Inputs and Outputs. The functionality works through the app.callback decorator. Inputs and Outputs are simply the properties of a component that a user can interact with. For example, an Input could be the option you select from a droplist and an Output could be a visualization.

A Simple Example

I’ll show you how easy it is to make a simple dashboard before we get into the complex example. Name the file index.py.

Import Dependencies and Data

I’m reading from a sqlite database, but you can read a csv file using pandas if you’re not familiar with sqlite.

import dash
import dash_bootstrap_components as dbc
import dash_core_components as dcc
import dash_html_components as html
from dash.dependencies import Input, Output
import dash_table
import pandas as pd
import sqlite3

import plotly.graph_objs as go
conn = sqlite3.connect(r"C:\Users\MTGro\Desktop\coding\wineApp\db\wine_data.sqlite")
c = conn.cursor()
df = pd.read_sql("select * from wine_data", conn)
df = df[['country', 'description', 'rating', 'price','province','title','variety','winery','color']]
df.head(1)
Image for post
Image for post
First row of the dataset

Configure the Dash App

To configure the app to run, you’ll need to include this bit of boilerplate code. Notice this is where the external style sheet is set. Since I am using Bootstrap CSS, I set it like using [dbc.themes.BOOTSTRAP]

app = dash.Dash(__name__, external_stylesheets = [dbc.themes.BOOTSTRAP])
server = app.server
app.config.suppress_callback_exceptions = True

Add the Layout

Remember, the first pillar of the Dash app is the tree of components that form the Layout. Since the app has two tabs, I’ll start by rendering the tabs in the layout. An HTML <div> Tag defines a division or a section in an HTML document. Learn more about html here.

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

Take note of the id set for dcc.Tabs and the values set for dcc.Tab. Those are the Input for the callback I’ll write to make the tabs interactive and return a layout. As the Output for the callback, I will use the id value “tabs-content” from html.Div(id=’tabs-content’). If I were to run the app without the callback, it would look like this:

Image for post
Image for post
Two tabs, no content

Add Callbacks

Let there be interaction! The values I specified as Inputs and Output are properties of particular components. This is where Dash provides the magic… to quote their documentation:

Whenever an input property changes, the function that the callback decorator wraps will get called automatically. Dash provides the function with the new value of the input property as an input argument and Dash updates the property of the output component with whatever was returned by the function.

#callback to control the tab content
@app.callback(Output('tabs-content', 'children'),
[Input('tabs', 'value')])
def render_content(tab):
if tab == 'tab-1':
return html.H1('Tab 1')
elif tab == 'tab-2':
return html.H1('Tab 2')

This callback uses the value from the dcc.Tab to determine which html value to return as the child for id “tabs-content”

Image for post
Image for post
Tab 2

Add Data to the App

Now that the layout functionality is in place, add data to the tabs. Tab one contains table data, which displays in the app using the Dash component library DataTable. Since the dataset contains over 100k rows, I decided to use backend paging and filtering using Pandas. This looks like a lot of code, but keep in mind, most of it come directly from the Dash documentation. Dive into the documentation to learn more about styling and functionality!

The DataTable Layout

@app.callback(Output('tabs-content', 'children'),
[Input('tabs', 'value')])
def render_content(tab):
if tab == 'tab-1':
return html.Div(dash_table.DataTable(
id='table-sorting-filtering',
columns=[
{'name': i, 'id': i, 'deletable': True} for i in df.columns
],
style_table={'overflowX': 'scroll'},
style_cell={
'height': '90',
# all three widths are needed
'minWidth': '140px', 'width': '140px', 'maxWidth': '140px',
'whiteSpace': 'normal'
},
page_current= 0,
page_size= 50,
page_action='custom',
filter_action='custom',
filter_query='',
sort_action='custom',
sort_mode='multi',
sort_by=[]
)
)

The Scatter Plot Layout

The scatter plot is produced by using the Dash Core Component Graph. It is also possible to use Plotly with the component. I’ll show how to do that in the more advanced example at the end.

elif tab == 'tab-2':
return html.Div([
dcc.Graph(
id='rating-price',
figure={
'data': [
dict(
y = df['price'],
x = df['rating'],
mode ='markers',
opacity = 0.7,
marker = {
'size': 8,
'line': {'width': 0.5, 'color': 'white'}
},
name = 'Price v Rating'
)
],
'layout': dict(
xaxis = {'type': 'log', 'title': 'Rating'},
yaxis = {'title': 'Price'},
margin = {'l': 40, 'b': 40, 't': 10, 'r': 10},
legend = {'x': 0, 'y': 1},
hovermode = 'closest'
)
}
)
])

The DataTable Callbacks

This is boilerplate code taken from the documentation for the Dash DataTable Component library. Additional inputs can be added to callback to apply side panel controlled filtering.

operators = [['ge ', '>='],
['le ', '<='],
['lt ', '<'],
['gt ', '>'],
['ne ', '!='],
['eq ', '='],
['contains '],
['datestartswith ']]
def split_filter_part(filter_part):
for operator_type in operators:
for operator in operator_type:
if operator in filter_part:
name_part, value_part = filter_part.split(operator, 1)
name = name_part[name_part.find('{') + 1: name_part.rfind('}')]
value_part = value_part.strip()
v0 = value_part[0]
if (v0 == value_part[-1] and v0 in ("'", '"', '`')):
value = value_part[1: -1].replace('\\' + v0, v0)
else:
try:
value = float(value_part)
except ValueError:
value = value_part
# word operators need spaces after them in the filter string,
# but we don't want these later
return name, operator_type[0].strip(), value
return [None] * 3@app.callback(Output('table-sorting-filtering', 'data'),
[Input('table-sorting-filtering', "page_current"),
Input('table-sorting-filtering', "page_size"),
Input('table-sorting-filtering', 'sort_by'),
Input('table-sorting-filtering', 'filter_query')])
def update_table(page_current, page_size, sort_by, filter):
filtering_expressions = filter.split(' && ')
dff = df
for filter_part in filtering_expressions:
col_name, operator, filter_value = split_filter_part(filter_part)
if operator in ('eq', 'ne', 'lt', 'le', 'gt', 'ge'):
# these operators match pandas series operator method names
dff = dff.loc[getattr(dff[col_name], operator)(filter_value)]
elif operator == 'contains':
dff = dff.loc[dff[col_name].str.contains(filter_value)]
elif operator == 'datestartswith':
# this is a simplification of the front-end filtering logic,
# only works with complete fields in standard format
dff = dff.loc[dff[col_name].str.startswith(filter_value)]
if len(sort_by):
dff = dff.sort_values(
[col['column_id'] for col in sort_by],
ascending=[
col['direction'] == 'asc'
for col in sort_by
],
inplace=False
)
page = page_current
size = page_size
return dff.iloc[page * size: (page + 1) * size].to_dict('records')

Run the App

if __name__ == "__main__":
app.run_server()

Once the code is complete, run the app through the console using python index.py

Image for post
Image for post
python index.py
Image for post
Image for post
Simple Tab Dash App

Adding the Sidepanel and File Structure

The app is functional, but as you can tell, expanding the number of tabs and callbacks will make the code feel long and hard to manage. Instead, break the content down into separate files.

The File Structure

I structured the files in a way that made it easy to add additional tabs and functionality without bloating a single file or impacting the layout of the other tabs. Need to add a new tab? EASY! Just add a new Tab file and update the index.py file to look for the new tab.

Image for post
Image for post

App.py

Since I am adding callbacks to elements that don’t exist in the app.layout as they are spread throughout files, I set suppress_callback_exceptions = True

import dash
import dash_bootstrap_components as dbc
app = dash.Dash(__name__, external_stylesheets = [dbc.themes.BOOTSTRAP])
server = app.serverapp.config.suppress_callback_exceptions = True

Index.py

This is the file that will be run in the console. The app.layout returns sidepanel.layout. It also contains majority of the callbacks used in the app. The functionality for the sidepanel is written into the callbacks.

import dash
import plotly
import dash_core_components as dcc
import dash_html_components as html
import dash_table
from dash.dependencies import Input, Output
import dash_bootstrap_components as dbc
import sqlite3
import pandas as pd
from app import app
from tabs import sidepanel, tab1, tab2
from database import transforms
app.layout = sidepanel.layout@app.callback(Output('tabs-content', 'children'),
[Input('tabs', 'value')])
def render_content(tab):
if tab == 'tab-1':
return tab1.layout
elif tab == 'tab-2':
return tab2.layout
operators = [['ge ', '>='],
['le ', '<='],
['lt ', '<'],
['gt ', '>'],
['ne ', '!='],
['eq ', '='],
['contains '],
['datestartswith ']]
def split_filter_part(filter_part):
for operator_type in operators:
for operator in operator_type:
if operator in filter_part:
name_part, value_part = filter_part.split(operator, 1)
name = name_part[name_part.find('{') + 1: name_part.rfind('}')]
value_part = value_part.strip()
v0 = value_part[0]
if (v0 == value_part[-1] and v0 in ("'", '"', '`')):
value = value_part[1: -1].replace('\\' + v0, v0)
else:
try:
value = float(value_part)
except ValueError:
value = value_part
# word operators need spaces after them in the filter string,
# but we don't want these later
return name, operator_type[0].strip(), value
return [None] * 3@app.callback(
Output('table-sorting-filtering', 'data')
, [Input('table-sorting-filtering', "page_current")
, Input('table-sorting-filtering', "page_size")
, Input('table-sorting-filtering', 'sort_by')
, Input('table-sorting-filtering', 'filter_query')
, Input('rating-95', 'value')
, Input('price-slider', 'value')
])
def update_table(page_current, page_size, sort_by, filter, ratingcheck, prices):
filtering_expressions = filter.split(' && ')
dff = transforms.df
print(ratingcheck)
low = prices[0]
high = prices[1]
dff = dff.loc[(dff['price'] >= low) & (dff['price'] <= high)]if ratingcheck == ['Y']:
dff = dff.loc[dff['rating'] >= 95]
else:
dff
for filter_part in filtering_expressions:
col_name, operator, filter_value = split_filter_part(filter_part)
if operator in ('eq', 'ne', 'lt', 'le', 'gt', 'ge'):
# these operators match pandas series operator method names
dff = dff.loc[getattr(dff[col_name], operator)(filter_value)]
elif operator == 'contains':
dff = dff.loc[dff[col_name].str.contains(filter_value)]
elif operator == 'datestartswith':
# this is a simplification of the front-end filtering logic,
# only works with complete fields in standard format
dff = dff.loc[dff[col_name].str.startswith(filter_value)]
if len(sort_by):
dff = dff.sort_values(
[col['column_id'] for col in sort_by],
ascending=[
col['direction'] == 'asc'
for col in sort_by
],
inplace=False
)
page = page_current
size = page_size
return dff.iloc[page * size: (page + 1) * size].to_dict('records')

DataBase > transforms.py

The transforms file contains any transformations the data needs to go through before being called into the app.

import dash
import dash_bootstrap_components as dbc
import pandas as pd
import sqlite3
from dash.dependencies import Input, Output
conn = sqlite3.connect(r"C:\Users\MTGro\Desktop\coding\wineApp\db\wine_data.sqlite")
c = conn.cursor()
df = pd.read_sql("select * from wine_data", conn)df = df[['country', 'description', 'rating', 'price', 'province', 'title','variety','winery','color','varietyID']]

Tabs > sidepanel.py

This is where the power of Bootstrap CSS comes in. The layout grid makes it easy to control the look of the layout. There are three main layout components in dash-bootstrap-components: Container, Row, and Col.

import dash
import plotly
import dash_core_components as dcc
import dash_html_components as html
import dash_bootstrap_components as dbc
import dash_table
import pandas
from dash.dependencies import Input, Output
from app import appfrom tabs import tab1, tab2
from database import transforms
df = transforms.df
min_p=df.price.min()
max_p=df.price.max()
layout = html.Div([
html.H1('Wine Dash')
,dbc.Row([dbc.Col(
html.Div([
html.H2('Filters')
, dcc.Checklist(id='rating-95'
, options = [
{'label':'Only rating >= 95 ', 'value':'Y'}
])
,html.Div([html.H5('Price Slider')
,dcc.RangeSlider(id='price-slider'
,min = min_p
,max= max_p
, marks = {0: '$0',
500: '$500',
1000: '$1000',
1500: '$1500',
2000: '$2000',
2500: '$2500',
3000: '$3000',
}
, value = [0,3300]
)

])

], style={'marginBottom': 50, 'marginTop': 25, 'marginLeft':15, 'marginRight':15})
, width=3)
,dbc.Col(html.Div([
dcc.Tabs(id="tabs", value='tab-1', children=[
dcc.Tab(label='Data Table', value='tab-1'),
dcc.Tab(label='Scatter Plot', value='tab-2'),
])
, html.Div(id='tabs-content')
]), width=9)])

])

Tabs > Tab1

This is the DataTable from the example at the beginning of the article. The callbacks are in the index. The callbacks include functionality for filtering using the sidepanel components.

import dash
import plotly
import dash_core_components as dcc
import dash_html_components as html
import dash_bootstrap_components as dbc
import dash_table
import pandas as pd
from dash.dependencies import Input, Output
from app import app
from database import transforms
df = transforms.dfPAGE_SIZE = 50layout =html.Div(dash_table.DataTable(
id='table-sorting-filtering',
columns=[
{'name': i, 'id': i, 'deletable': True} for i in df[['country','description','rating','price','province','title','variety','winery','color']]
],
style_table={'height':'750px'
,'overflowX': 'scroll'},
style_data_conditional=[
{
'if': {'row_index': 'odd'},
'backgroundColor': 'rgb(248, 248, 248)'
}
],
style_cell={
'height': '90',
# all three widths are needed
'minWidth': '140px', 'width': '140px', 'maxWidth': '140px', 'textAlign': 'left'
,'whiteSpace': 'normal'
}
,style_cell_conditional=[
{'if': {'column_id': 'description'},
'width': '48%'},
{'if': {'column_id': 'title'},
'width': '18%'},
]
, page_current= 0,
page_size= PAGE_SIZE,
page_action='custom',
filter_action='custom',
filter_query='',
sort_action='custom',
sort_mode='multi',
sort_by=[]
)
)

Tabs > Tab2

Instead of using the Dash Core component Graph, I am using Plotly’s Scattergl to get better performance visualizing the dataset.

import dash
import dash_core_components as dcc
import dash_html_components as html
import dash_bootstrap_components as dbc
import pandas as pd
import plotly.graph_objs as go
from dash.dependencies import Input, Output
import dash_table
from app import app
from database import transforms
df = transforms.dflayout = html.Div(
id='table-paging-with-graph-container',
className="five columns"
)
@app.callback(Output('table-paging-with-graph-container', "children"),
[Input('rating-95', 'value')
, Input('price-slider', 'value')
])
def update_graph(ratingcheck, prices):
dff = df
low = prices[0]
high = prices[1]
dff = dff.loc[(dff['price'] >= low) & (dff['price'] <= high)]

if ratingcheck == ['Y']:
dff = dff.loc[dff['rating'] >= 95]
else:
dff
trace1 = go.Scattergl(x = dff['rating']
, y = dff['price']
, mode='markers'
, opacity=0.7
, marker={
'size': 8
, 'line': {'width': 0.5, 'color': 'white'}
}
, name='Price v Rating'
)
return html.Div([
dcc.Graph(
id='rating-price'
, figure={
'data': [trace1],
'layout': dict(
xaxis={'type': 'log', 'title': 'Rating'},
yaxis={'title': 'Price'},
margin={'l': 40, 'b': 40, 't': 10, 'r': 10},
legend={'x': 0, 'y': 1},
hovermode='closest'
)
}
)
])
Image for post
Image for post

If you’re interested in learning where to host your dashboard or other data science projects, check out my article on hosting options!

Thanks for checking out my beginners guide to creating a Dash app. You can find the code on github here:

The Startup

Medium's largest active publication, followed by +771K people. Follow to join our community.

Eric Kleppen

Written by

Software Product Analyst in Data Science. pythondashboards.com Top writer in Business www.linkedin.com/in/erickleppen01/

The Startup

Medium's largest active publication, followed by +771K people. Follow to join our community.

Eric Kleppen

Written by

Software Product Analyst in Data Science. pythondashboards.com Top writer in Business www.linkedin.com/in/erickleppen01/

The Startup

Medium's largest active publication, followed by +771K people. Follow to join our community.

Medium is an open platform where 170 million readers come to find insightful and dynamic thinking. Here, expert and undiscovered voices alike dive into the heart of any topic and bring new ideas to the surface. Learn more

Follow the writers, publications, and topics that matter to you, and you’ll see them on your homepage and in your inbox. Explore

If you have a story to tell, knowledge to share, or a perspective to offer — welcome home. It’s easy and free to post your thinking on any topic. Write on Medium

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store