Dashboards in Python for Beginners using Dash — Responsive Mobile Dashboards with Bootstrap CSS Components

Examples of Bootstrap Components for Responsive Dashboards

Eric Kleppen
Feb 28 · 15 min read
Image for post
Image for post
https://www.vecteezy.com/vector-art/173643-spreadsheet-icon-set

Why Responsive Design

Image for post
Image for post
https://upload.wikimedia.org/wikipedia/commons/7/7b/Responsive_Web_Design_for_Desktop%2C_Notebook%2C_Tablet_and_Mobile_Phone.png
Image for post
Image for post
The Completed Dashboard (zoomed out 50% for effect)

Getting into CSS and Dash

Layout

Callbacks

What is CSS?

Image for post
Image for post
Example CSS File

Bootstrap CSS in Dash

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

Installation and Dependencies

pip install dash-bootstrap-componentsORconda install -c conda-forge dash-bootstrap-components
import dashimport dash_bootstrap_components as dbcimport dash_core_components as dcc
import dash_html_components as html

Exploring Bootstrap Layout Components

import dash
import dash_bootstrap_components as dbc
import dash_core_components as dcc
import dash_html_components as html
app = dash.Dash(__name__, external_stylesheets=[dbc.themes.BOOTSTRAP])body = html.Div([
html.H1("Bootstrap Grid System Example")
, dbc.Row(dbc.Col(html.Div(dbc.Alert("This is one column", color="primary"))))
, dbc.Row([
dbc.Col(html.Div(dbc.Alert("One of three columns", color="primary")))
, dbc.Col(html.Div(dbc.Alert("One of three columns", color="primary")))
, dbc.Col(html.Div(dbc.Alert("One of three columns", color="primary")))
])
])
app.layout = html.Div([body])
if __name__ == "__main__":
app.run_server(debug = True)
Image for post
Image for post
body = html.Div([html.H1("Bootstrap Grid System Example")
, html.H4("no_gutters = False")
, dbc.Row([
dbc.Col(html.Div(dbc.Alert("One of two columns")), width=3),
dbc.Col(html.Div(dbc.Alert("One of two columns")), width=3),
dbc.Col(html.Div(dbc.Alert("One of two columns")), width=3),
dbc.Col(html.Div(dbc.Alert("One of two columns")), width=3)
])
, html.H4("no_gutters = True")
, dbc.Row([
dbc.Col(html.Div(dbc.Alert("One of two columns")), width=3),
dbc.Col(html.Div(dbc.Alert("One of two columns")), width=3),
dbc.Col(html.Div(dbc.Alert("One of two columns")), width=3),
dbc.Col(html.Div(dbc.Alert("One of two columns")), width=3)
], no_gutters = True)
, html.H3("Examples of justify property")
, html.H4("start, center, end, between, around")
, dbc.Row([
dbc.Col(html.Div(dbc.Alert("One of two columns")), width=4),
dbc.Col(html.Div(dbc.Alert("One of two columns")), width=4),
],
justify="start")
, dbc.Row([
dbc.Col(html.Div(dbc.Alert("One of two columns")), width=3),
dbc.Col(html.Div(dbc.Alert("One of two columns")), width=3),
],
justify="center")
, dbc.Row([
dbc.Col(html.Div(dbc.Alert("One of three columns")), width=3)
, dbc.Col(html.Div(dbc.Alert("One of three columns")), width=3)
, dbc.Col(html.Div(dbc.Alert("One of three columns")), width=3)
],
justify="end")
, dbc.Row([
dbc.Col(html.Div(dbc.Alert("One of two columns")), width=3),
dbc.Col(html.Div(dbc.Alert("One of two columns")), width=3)
, dbc.Col(html.Div(dbc.Alert("One of three columns")), width=3)
],
justify="between")
, dbc.Row([
dbc.Col(html.Div(dbc.Alert("One of two columns")), width=4),
dbc.Col(html.Div(dbc.Alert("One of two columns")), width=4),
],
justify="around")
, html.H4("Container Example")
, dbc.Container([
dbc.Row([
dbc.Col(html.Div(dbc.Alert("One of two columns")), width=3),
dbc.Col(html.Div(dbc.Alert("One of two columns")), width=3),
dbc.Col(html.Div(dbc.Alert("One of two columns")), width=3),
dbc.Col(html.Div(dbc.Alert("One of two columns")), width=3)
])
, html.H4("no_gutters = True")
, dbc.Row([
dbc.Col(html.Div(dbc.Alert("One of two columns")), width=3),
dbc.Col(html.Div(dbc.Alert("One of two columns")), width=3),
dbc.Col(html.Div(dbc.Alert("One of two columns")), width=3),
dbc.Col(html.Div(dbc.Alert("One of two columns")), width=3)
], no_gutters = True)
])
])
Image for post
Image for post
Bootstrap Grid System
body = html.Div([
html.H1("Bootstrap Grid System Example")
, dbc.Row(dbc.Col(html.Div(dbc.Alert("This is one column", color="primary"))))
, dbc.Row([
dbc.Col(html.Div(dbc.Alert("One of three columns", color="primary")), lg=3, md=4, xs=12)
, dbc.Col(html.Div(dbc.Alert("One of three columns", color="primary")), lg=3, md=4, xs=12)
, dbc.Col(html.Div(dbc.Alert("One of three columns", color="primary")), lg=3, md=4, xs=12)
])
])
Image for post
Image for post
Large (lg = 3)
Image for post
Image for post
Medium (md = 4)
Image for post
Image for post
Extra Small (xs = 12)

Enhancing a Dashboard

Image for post
Image for post
Dashboard on small screen

Starter Code

import dash
import dash_html_components as html
import dash_core_components as dcc
import dash_table
import pandas as pd
import praw
import pandas as pd
from dash.dependencies import Input, Output, State
from dash.exceptions import PreventUpdate
from config import cid, csec, uag
reddit = praw.Reddit(client_id= cid, client_secret= csec, user_agent= uag)posts = []new_bets = reddit.subreddit('wallstreetbets').new(limit=100)for post in new_bets:
posts.append([post.title, post.score, post.num_comments, post.selftext, post.created, post.pinned, post.total_awards_received])
posts = pd.DataFrame(posts,columns=['title', 'score', 'comments', 'post', 'created', 'pinned', 'total awards'])
def get_features(dataframe):
df = posts.copy()
df['words'] = df['post'].apply(lambda x : len(x.split()))
df['chars'] = df['post'].apply(lambda x : len(x.replace(" ","")))
df['word density'] = (df['words'] / (df['chars'] + 1)).round(3)
df['unique words'] = df['post'].apply(lambda x: len(set(w for w in x.split())))
df['unique density'] = (df['unique words'] / df['words']).round(3)
return dfdf = get_features(posts)app.layout = html.Div([
html.P(html.Button('Refresh', id='refresh'))
,html.P(html.Div(html.H3('Enter Subreddit')))
,dcc.Input(id='input-1-state', type='text', value='wallstreetbets')
,dash_table.DataTable(
id='table'
,columns=[{"name": i, "id": i} for i in df.columns]
,fixed_rows={ 'headers': True, 'data': 0 }
,data=df.to_dict('records')
)
])
@app.callback(Output('table', 'data'),
[Input('refresh', 'n_clicks')],
[State('input-1-state', 'value')
])
def update_data(n_clicks, subreddits):
dff = df
if subreddits is None:
subreddits = 'wallstreetbets'
else:
subreddits
if n_clicks is None:
raise PreventUpdate
else:
posts = []
new_bets = reddit.subreddit(subreddits).hot(limit=100)
for post in new_bets:
posts.append([post.title, post.score, post.num_comments, post.selftext, post.created, post.pinned, post.total_awards_received])
posts = pd.DataFrame(posts,columns=['title', 'score', 'comments', 'post', 'created', 'pinned', 'total awards'])
dff = get_features(posts)

return dff.to_dict('records')

Adding Jumbotron

Image for post
Image for post
The Jumbotron
# Original
html.Div([
html.P(html.Button('Refresh', id='refresh'))
,html.P(html.Div(html.H3('Enter Subreddit')))
,dcc.Input(id='input-1-state', type='text', value='wallstreetbets')
])
dbc.Jumbotron([
dbc.Row(dbc.Col([

html.P(html.Div(html.H3('Enter Subreddit')))
, dcc.Input(id='input-1-state', type='text', value='wallstreetbets')
, html.P(html.Button('Generate Graphs', id='refresh'))
], width = 6)
, justify = 'center')# end row
], fluid = True)

Adding Modal

Image for post
Image for post
Modal Example
dbc.Jumbotron([
dbc.Row(dbc.Col([
html.P(html.Div(html.H3('Enter Subreddit')))
, dcc.Input(id='input-1-state', type='text', value='wallstreetbets')
, html.P(html.Button('Generate Graphs', id='refresh'))
, dbc.Modal(
[
dbc.ModalHeader("Generating"),
dbc.ModalBody("The graphs have generated"),
dbc.ModalFooter(
dbc.Button("Close", id="close", className="ml-auto")
),
],
id="modal",
)

], width = 6)
, justify = 'center')# end row
], fluid = True)
@app.callback(
Output("modal", "is_open"),
[Input("open", "n_clicks"), Input("close", "n_clicks")],
[State("modal", "is_open")],
)
def toggle_modal(n1, n2, is_open):
if n1 or n2:
return not is_open
return is_open

Adding Accordion with Collapse

Image for post
Image for post
Accordion Example
def make_item(i):
# we use this function to make the example items to avoid code duplication
graph_info = [0,'Displays words on x axis vs score on y axis.'
, 'Displays words on x axis vs word density on y axis.'
, 'Displays words on x axis vs unique density on y axis.']
return dbc.Card(
[
dbc.CardHeader(
html.H2(
dbc.Button(
f"Graph {i} Info",
color="link",
id=f"group-{i}-toggle",
)
)
),
dbc.Collapse(
dbc.CardBody(graph_info[i]),
id=f"collapse-{i}",
),
]
)
Displays words on x axis vs score on y axis.

Constructing the Accordion

accordion = html.Div([make_item(1), make_item(2), make_item(3)], className="accordion")

Accordion Callback

@app.callback(
[Output(f"collapse-{i}", "is_open") for i in range(1, 4)],
[Input(f"group-{i}-toggle", "n_clicks") for i in range(1, 4)],
[State(f"collapse-{i}", "is_open") for i in range(1, 4)],
)
def toggle_accordion(n1, n2, n3, is_open1, is_open2, is_open3):
ctx = dash.callback_context
if not ctx.triggered:
return ""
else:
button_id = ctx.triggered[0]["prop_id"].split(".")[0]
if button_id == "group-1-toggle" and n1:
return not is_open1, False, False
elif button_id == "group-2-toggle" and n2:
return False, not is_open2, False
elif button_id == "group-3-toggle" and n3:
return False, False, not is_open3
return False, False, False

The Complete Dashboard

The Code

import dash
import dash_html_components as html
import dash_core_components as dcc
import dash_table
import pandas as pd
import praw
import pandas as pd
from dash.dependencies import Input, Output, State
from dash.exceptions import PreventUpdate
from config import cid, csec, uag
reddit = praw.Reddit(client_id= cid, client_secret= csec, user_agent= uag)posts = []new_bets = reddit.subreddit('wallstreetbets').hot(limit=100)for post in new_bets:
posts.append([post.title, post.score
, post.num_comments, post.selftext
, post.created, post.pinned, post.total_awards_received])
posts = pd.DataFrame(posts,columns=['title', 'score', 'comments', 'post', 'created', 'pinned', 'total awards'])
def get_features(dataframe):
df = posts.copy()
df['words'] = df['post'].apply(lambda x : len(x.split()))
df['chars'] = df['post'].apply(lambda x : len(x.replace(" ","")))
df['word density'] = (df['words'] / (df['chars'] + 1)).round(3)
df['unique words'] = df['post'].apply(lambda x: len(set(w for w in x.split())))
df['unique density'] = (df['unique words'] / df['words']).round(3)
return dfdf = get_features(posts)def make_item(i):
# we use this function to make the example items to avoid code duplication
graph_info = [0,'Displays words on x axis vs score on y axis.'
, 'Displays words on x axis vs word density on y axis.'
, 'Displays words on x axis vs unique density on y axis.']
return dbc.Card(
[
dbc.CardHeader(
html.H2(
dbc.Button(
f"Graph {i} Info",
color="link",
id=f"group-{i}-toggle",
)
)
),
dbc.Collapse(
dbc.CardBody(graph_info[i]),
id=f"collapse-{i}",
),
]
)
accordion = html.Div(
[make_item(1), make_item(2), make_item(3)], className="accordion"
)
app = dash.Dash(__name__, external_stylesheets=[dbc.themes.BOOTSTRAP])app.layout = html.Div([dbc.Jumbotron([dbc.Row(dbc.Col([
html.P(html.Div(html.H3('Enter Subreddit')))
, dcc.Input(id='input-1-state', type='text', value='wallstreetbets')
, html.P(html.Button('Generate Graphs', id='refresh'))
, dbc.Modal(
[
dbc.ModalHeader("Generating"),
dbc.ModalBody("The graphs have generated"),
dbc.ModalFooter(
dbc.Button("Close", id="close", className="ml-auto")
),
],
id="modal",
)
], width = 6)
, justify = 'center')# end row
], fluid = True)

, html.Div(dbc.Row(dbc.Col(accordion ,width = 8),justify = 'center'))

, html.Div([
dbc.Row(dbc.Col(dash_table.DataTable(
id='table'
, columns=[{"name": i, "id": i} for i in df.columns]
, data=df.to_dict('records')
#, fixed_rows={ 'headers': True, 'data': 0 }
, style_cell_conditional=[
{'if': {'column_id': 'title'},
'width': '150px'
,'padding': '15px'},
{'if': {'column_id': 'post'},
'width': '400px'
}
,{'if': {'column_id': 'words'},
'width': '65px'
}
,{'if': {'column_id': 'chars'},
'width': '65px'
}
]
,style_cell={
'overflowX': 'hidden',
'textOverflow': 'ellipsis',
'maxWidth': '25px',
'textAlign': 'left'
}
, style_table={
'maxHeight': '550px'
#,'maxWidth': '800px'
,'overflowY': 'scroll'
,'overflowX': 'hidden'
}
), width = 10), justify = 'center')# end dt
], style = {'background-color': 'lightblue'
#,'margin-bottom': '5px'
, 'padding' : '50px'
}) #end div
, dbc.Container(
html.Div(id = 'graph-container')
)
])#end div@app.callback(
[Output(f"collapse-{i}", "is_open") for i in range(1, 4)],
[Input(f"group-{i}-toggle", "n_clicks") for i in range(1, 4)],
[State(f"collapse-{i}", "is_open") for i in range(1, 4)],
)
def toggle_accordion(n1, n2, n3, is_open1, is_open2, is_open3):
ctx = dash.callback_context
if not ctx.triggered:
return ""
else:
button_id = ctx.triggered[0]["prop_id"].split(".")[0]
if button_id == "group-1-toggle" and n1:
return not is_open1, False, False
elif button_id == "group-2-toggle" and n2:
return False, not is_open2, False
elif button_id == "group-3-toggle" and n3:
return False, False, not is_open3
return False, False, False
@app.callback(
Output("modal", "is_open"),
[Input("refresh", "n_clicks"), Input("close", "n_clicks")],
[State("modal", "is_open")],
)
def toggle_modal(n1, n2, is_open):
if n1 or n2:
return not is_open
return is_open
@app.callback(
Output('graph-container', "children"),
[Input('table', "data")])
def update_graph(rows):
dff = pd.DataFrame(rows)
return html.Div(
[
dcc.Graph(
id=column,
figure={
"data": [
{
"x": dff["words"],
"y": dff[column] if column in dff else [],
"type": "bar",
"marker": {"color": "#0074D9"},
}
],
"layout": {
"xaxis": {"automargin": True},
"yaxis": {"automargin": True},
"height": 250,
"margin": {"t": 50, "l": 10, "r": 10},
"title": column
},
},
)
for column in ["score", "word density", "unique density"]
]
)
@app.callback(Output('table', 'data'),
[Input('refresh', 'n_clicks')],
[State('input-1-state', 'value')
])
def update_data(n_clicks, subreddits):
dff = df
if subreddits is None:
subreddits = 'wallstreetbets'
else:
subreddits
if n_clicks is None:
raise PreventUpdate
else:
posts = []
new_bets = reddit.subreddit(subreddits).hot(limit=100)
for post in new_bets:
posts.append([post.title, post.score, post.num_comments, post.selftext, post.created, post.pinned, post.total_awards_received])
posts = pd.DataFrame(posts,columns=['title', 'score', 'comments', 'post', 'created', 'pinned', 'total awards'])
dff = get_features(posts)

return dff.to_dict('records')
if __name__ == '__main__':
app.run_server(debug=True)

The Startup

Medium's largest active publication, followed by +720K 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 +720K 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 +720K 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.

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

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.

Get the Medium app