How to build a dashboard with Bootstrap 4 from scratch (Part 1)
By: Ilya Bondarev
Tags: Bootstrap, Untappd, Leftjoin, Dash, Plotly

In previous articles we reviewed Plotly’s Dash Framework, learned to build scatter plots and create a map visualization. This time we will summarize our knowledge and put all the pieces together to design a dashboard layout using the Bootstrap 4 grid system.

To facilitate the development, we’ll refer to the dash-bootstrap-components library. This is a great tool that integrates Bootstrap in Dash, allowing us to write web pages in pure Python, and add any Bootstrap components and styling.

Draft Layout

Before we begin coding it’s crucial to have a plan of our app, a rough layout that would help us to see the big picture and quickly modify the structure. We used draw.io to make a dashboard draft, this application enables to create diagrams, graphs, flowcharts, and forms at the click of a button. The dashboard will be built according to this template:

Like the dashboard itself, the top header will be colored in gold and white, the main colors of Untappd. Just below the header, there is a section with breweries, which includes a scatter plot and a control panel. And at the bottom of the page, there will be a map showing beverage rating across the regions of Russia.

All right, let’s get started, first create a new python file with the name application.py. The file will store all the front end components of the dashboard, and create a new directory named assets. The directory structure should be similar:

- application.py

- assets/

|-- typography.css

|-- header.css

|-- custom-script.js

|-- image.png

Then we import the libraries and initialize our application:

import dash

import dash_bootstrap_components as dbc

import dash_html_components as html

import dash_core_components as dcc

import pandas as pd

from get_ratio_scatter_plot import get_plot

from get_russian_map import get_map

from clickhouse_driver import Client

from dash.dependencies import Input, Output standard_BS = dbc.themes.BOOTSTRAP

app = dash.Dash(__name__, external_stylesheets=[standard_BS])

Main parameters of the app:

__name__ — to enable access to static elements stored in the assets folder (such as images, CSS and JS files)

external_stylesheets — external CSS styling, here we are using a standard Bootstrap theme, however you can create your own theme or use any of the availables ones.

Hook up a few more things to work with local files and connect to the Clickhouse Database:

app.scripts.config.serve_locally = True

app.css.config.serve_locally = True client = Client(host='ec2-3-16-148-63.us-east-2.compute.amazonaws.com',

user='default',

password='',

port='9000',

database='default')

Add a palette of colors:

colors = ['#ffcc00',

'#f5f2e8',

'#f8f3e3',

'#ffffff',

]

Creating a layout

All the dashboard elements will be placed within a Bootstrap container, which is in the <div> block:

- app

|-- div

|-- container

|-- logo&header

|-- container

|-- div

|-- controls&scatter

|-- map app.layout = html.Div(

[

dbc.Container( < header>



dbc.Container(

html.Div(

[



< body >



],

),

fluid=False, style={'max-width': '1300px'},

),

],

style={'background-color': colors[1], 'font-family': 'Proxima Nova Bold'},

)

Here we set a fixed container width, background color, and font style of the page that is stored in typography.css in the assets folder. Let’s take a closer look at the first element in the div block, that’s the top header with the Untappd logo:

logo = html.Img(src=app.get_asset_url('logo.png'),

style={'width': "128px", 'height': "128px",

}, className='inline-image')

and the header:

header = html.H3("Russian breweries stats from Untappd", style={'text-transform': "uppercase"})

We used Bootstrap Forms to position these two elements on the same level.

logo_and_header = dbc.FormGroup(

[

logo,

html.Div(

[

header

],

className="p-5"

)

],

className='form-row',

)

The class name ‘p-5’ allows to increase padding and vertically align the title while specifying ‘form-row’ as the form class name we put the logo and header in one row. At this point, the top header should look the following:

Now we need to center the elements and add some colors. Create a separate container that will take one row. Specify ‘d-flex justify-content-center’ in the className to achieve the same output.

dbc.Container(

dbc.Row(

[

dbc.Col(

html.Div(

logo_and_header,

),

),

],

style={'max-height': '128px',

'color': 'white',

} ),

className='d-flex justify-content-center',

style={'max-width': '100%',

'background-color': colors[0]},

),

And now the top header is done:

We’re approaching the main part, create the next Bootstrap Container and add a subheading:

dbc.Container(

html.Div(

[

html.Br(),

html.H5("Breweries", style={'text-align':'center', 'text-transform': 'uppercase'}),

html.Hr(), # horizontal break

The main body will consist of Bootstrap Cards, they can provide a structured layout of all parts, giving each element a clear border and saving the white space. Create the next element, a control panel with sliders:

slider_day_values = [1, 10, 20, 30, 40, 50, 60, 70, 80, 90, 100]

slider_top_breweries_values = [5, 25, 50, 75, 100, 125, 150, 175, 200] controls = dbc.Card(

[

dbc.CardBody(

[

dbc.FormGroup(

[

dbc.Label("Time Period", style={'text-align': 'center', 'font-size': '100%', 'text-transform': 'uppercase'}),

dcc.Slider(

id='slider-day',

min=1,

max=100,

step=10,

value=100,

marks={i: i for i in slider_day_values}

),

], style={'text-align': 'center'}

),

dbc.FormGroup(

[

dbc.Label("Number of breweries", style={'text-align': 'center', 'font-size': '100%', 'text-transform': 'uppercase'}),

dcc.Slider(

id='slider-top-breweries',

min=5,

max=200,

step=5,

value=200,

marks={i: i for i in slider_top_breweries_values}

),

], style={'text-align': 'center'}

),

],

)

],

style={'height': '32.7rem', 'background-color': colors[3]}

)

The control panel consists of two sliders that can be used to change the view on the scatter, they are positioned one below the other in a Bootstrap Form. The sliders were put inside the dbc.CardBody block, other elements will be added in the same way. It allows to eliminate alignment problem and achieve clear borders. By default, the sliders are painted in blue, but we can easily customize them by changing the properties of the class in sliders.css. Add the control panel with the scatter plot as follows:

dbc.Row(

[

dbc.Col(controls, width={"size": 4,

"order": 'first',

"offset": 0},

),

dbc.Col(dbc.Card(

[

dbc.CardBody(

[

html.H6("The ratio between the number of reviews and the average brewery rating",

className="card-title",

style={'text-transform': 'uppercase'}),

dcc.Graph(id='ratio-scatter-plot'),

],

),

],

style={'background-color': colors[2], 'text-align':'center'}

),

md=8),

],

align="start",

justify='center',

),

html.Br(),

And at the bottom of the page we will position the map:

html.H5("Venues and Regions", style={'text-align':'center', 'text-transform': 'uppercase',}),

html.Hr(), # horizontal break

dbc.Row(

[

dbc.Col(

dbc.Card(

[

dbc.CardBody(

[

html.H6("Average beer rating across regions",

className="card-title",

style={'text-transform': 'uppercase'},

),

dcc.Graph(figure=get_map())

],

),

],

style={'background-color': colors[2], 'text-align': 'center'}

),

md=12),

]

),

html.Br(),

Callbacks in Dash

Callback functions allow making dashboard elements interactive through the Input and Output properties of a particular component.

@app.callback(

Output('ratio-scatter-plot', 'figure'),

[Input('slider-day', 'value'),

Input('slider-top-breweries', 'value'),

]

)

def get_scatter_plots(n_days=100, top_n=200):

if n_days == 100 and top_n == 200:

df = pd.read_csv('data/ratio_scatter_plot.csv')

return get_plot(n_days, top_n, df)

else:

return get_plot(n_days, top_n)

In this example, our inputs are the “value” properties of the components that have the ids “slider-day’” and “slider-top-breweries”. Our output is the “children” property of the component with the id “ratio-scatter-plot”. When the input values are changed, the decorator function will be called automatically and the output on the scatter is updated. Learn more about callbacks from the examples in the docs.

It’s worth noting, that the scatter plot may not be displayed correctly when the page is loaded. To avoid this scenario we need to specify its initial state and produce a scatter plot from the saved CSV file stored in the data folder. Then, when changing the slider values, all data will be taken directly from the Clickhouse tables.

Add a few more lines responsible for deployment and our app is ready to run:

application = app.server if __name__ == '__main__':

application.run(debug=True, port=8000)

Next, we need to deploy our app to AWS BeansTalk and the first part of our Bootstrap Dashboard is completed!