Hands-on Tutorials

Create a professional dashboard with Dash and CSS Bootstrap

A step-by-step explained example available on Git and PythonAnywhere

Gabriele Albini
Towards Data Science
19 min readNov 1, 2020

--

Introduction

In this article, I am going through the steps I followed to create an interactive dashboard, using PlotlyDash, a library for Python and R, and enhancing the layout with CSS Bootstrap.

Dash library is written on top of Flask, Plotly.js, and React.js, that allows to build data visualization apps in pure Python, rendered via the web browser.

The final result I produced can be displayed here: https://dashdemo1.pythonanywhere.com

Image by author

This is a multi-page dashboard, simulating a business scenario, where there normally are some layout guidelines to be respected (corporate colors scheme, a pre-defined font, a corporate website that can inspire us, …) and some sales-related KPIs to be monitored.

The first page is filled with different types of charts (scatter plots, bar charts, bubble charts, heat-maps) and a recap table, with random data; the other pages are blank but useful to show how to build a multi-page structure.

1. Dashboard Objectives

Dashboards are intended to provide a clear and accurate view on some business-relevant KPIs, enabling the end user to understand what’s presented, find the information needed, apply some filters, and hopefully derive some conclusions.

This dashboard is designed like a website, composed by different pages with a top navigator bar.

Each page content is divided into three parts:

  • Some top filters that can be applied to all the charts displayed in the page
  • A recap table, also displayed at the top, which enables the user to quickly have a glance on the main KPIs. Ideally, the intention is for the user to avoid going through each and every chart on the page, but immediately spot where to focus on just by looking at the recap table. This recap table has some conditional color formatting, as a sort of traffic light, highlighting the positive / negative values.
  • Finally, a grid of charts, showing different views on the main KPIs

I have deliberately avoided offering any way for the user to export the underlying data: due to the rising concerns around data privacy and data loss, the dashboard is intended to use aggregated data only. Raw data points are unreachable to the end user.

2. Getting started with Dash

There are plenty of user guidelines and introductions to Plotly Dash.

I’ve personally found very useful to go through these sources, which explain plenty of examples:

Despite going through all these useful sources, I’ve had to face many challenges, especially in order to understand how to customize Dash components the way I wanted.

For this reason, I will also focus on this aspect in the steps below: the enhanced features and the layout, the visual identity of the end result is a very important aspect that can make the difference.

3. Configuring the folder structure for a multi-page dashboard

The app source-code files should be structured as recommended by the Dash guidelines (https://dash.plotly.com/urls) to enable a multi-page navigation. Based on this guide, I’ve used the following approach to minimize any code change or adaptation.

My root folder contains:

  • app.py : this file is very basic and will only define the app variable needed by Flask. I’ve simply followed the Plotly Dash guidelines to create this file.
  • index.py : this is a sort of navigator file, helping the app manage the URLs of the different pages. Also, this file is very standard: I recommend following the Plotly Dash guidelines for it, as we just need to customize the pathnames to have this working.
  • layouts.py : all pages html layouts will be stored in this file. Given that some components (like the header or the navbar) have to be replicated on each page, I’ve created some functions to return them, avoiding many repetitions within the code
  • callbacks.py : all callbacks (the functions behind the Dash components, that define the user interactions with the graphs) will be stored in this file

Within the root folder, Dash will look for:

  • a “data” subfolder: containing the underlying data of the dashboard
  • an “assets” subfolder: containing additional files, like: images; favorite icon, used by the browser; .css files

If this structure is followed for the assets subfolder, the app will automatically detect and use the favico.ico file or the .css files, with no code needed! Just name an image like “favicon.ico” and place it within root/assets and Dash will automatically use it.

There are some additional files within the root folder (such as the .gitgnore, README.md, Procfile, requirements.txt) : these files are needed to deploy or store the code on Git Hub.

4. Conceptualizing the layout with the CSS Bootstrap Grid system

With the app folder structure in place, the next thing to configure is the overall layout of our pages. We can’t talk about a web application layout without mentioning .css files, which centralize almost all layout properties and that can be read by the browser.

In very few words, .css files define the properties (fonts properties, sizes, colors, backgounds, …) of html components, also used by Dash.

The App will start applying the layout properties to the corresponding html components, reading them from the .css files stored in the “assets” folder of the app.

It is important to note that: if the properties for the same html component (eg. a H2 html title font family) are defined in multiple .css files, the last one read by the app will be applied (let’s ignore the !important css suffix for now).

CSS properties can also be defined by creating some custom “class”, then assigned to the html components (like a div) we want to style.

In my example, I’ve used 3 .css files, which I’ve named with some leading numbers to ensure that they are read by the app in a specific order:

  • A customized font, defining a new font family used by the app (you can download font families from google, copying their .css content. Note that, within the font file, there are some URLs, that the app will download. If your app will run in an offline environment, you should download the content of these URLs and place them in a subfolder: root/assets/fonts, adapting the url to this pathname, within the font .css file)
  • The bootstrap .css standard file, which I’ll describe just below (I basically downloaded the official CSS file from the Bootstrap website)
  • A “corporate-style.css” containing all customization I introduced on the layout

I’ve seen that many Plotly Dash examples use a basic .css template (https://codepen.io/chriddyp/pen/bWLwgP.css): this is not a requirement for the app and I chose not to use it for my dashboard (I’ve used the official .css Bootstrap file, instead).

4.1 Building the layout framework with CSS Bootstrap

CSS Boostrap provides a lot of predefined .css properties, classes and layout features we can reuse. CSS Bootstrap is famous for the known “grid systems” which help to scale websites pages depending on the device sizes used.

In short, we can think of our webpage as a table of rows and up to 12-columns: our html components can be placed inside this grid, which will have a dynamic size, according to the screen of the device it is displayed on.

I think this helped me a lot to obtain the layout I had in mind, which looked like the following:

Image by author

I’ve started thinking about the layout from the different sections (the “rows” of my page), and then picturing in my mind the different columns of the dashboard.

I’ve assumed that, like in a business scenario, some colors are already part of the corporate visual identity (in this case a sort of green palette with some pink elements) and I’ve picked a nice font-family called Dosis.

After that, I’ve been creating the scheme in the picture, with the different placeholders and the columns Bootstrap .css classes I’ve used.

Within the CSS Bootstrap grid system, different .div elements are nested, each with its class, starting from a row and then one or more columns. CSS Bootstrap has different columns classes, from col-1 to col-12, meaning that we can have the dynamic column width we wish, starting from a column which will be a 1/12 of the max width, till a column which will use the full width available (12/12). Within each column, it is possible to nest another row-columns sequence of .Div.

All these classes can be assigned to the html.Div([]) elements, within their className property.

This system helps to keep each section of the dashboard in order, with a designated area for each Dash component that is also dynamic to the screen used.

For instance, the following structure has been used to style the header, the navbar and the filters.

The header, consists of:

  • An external html.Div component, which has a .css “row” class, styled with a background color
  • Three html.Div components, inside the row; each represents a “col” .css class (remember that the total of these columns width must be equal to 12): a className=”2-col” div (a column of width 2/12), which will be kept empty, just to ensure that the title is centred; a 8-col div, hosting the main page title, and finally another 2-col div, hosting the corporate logo image
Image by author

The navbar, consists of:

  • Two empty col-3 div at the sides of the row
  • Three central col-2 div, each will contain a dcc.Link Dash component, that can be used to switch from one page to the other. The dcc.Link page for the current page is styled differently, to highlight the page the user is on
Image by author

The filters section consists of the following components:

Externally, we have a row and column div:

  • An external html.Div that has a “row sticky-top” class. This is a Bootstrap class that is the same as a row but that sticks at the top of the page, even when the user scrolls down.
  • Inside the row, there’s a 12-col Div with some styling properties

Within the 12-col Div column, we have another “row” div containing the following set of columns:

  • Two empty col-2 div at the sides of the internal row
  • Two col-4 div, internally, each containing filter components: a dcc.DatePickerRange and two dcc.Dropdown components
Image by author

4.2 Styling Dash components accessing to their CSS classes

Once the layout is defined, the next challenge, from a user-interface perspective, is to properly style each Dash component, such as date ranges (dcc.DatePickerRange), dropdown filters(dcc.Dropdown), etc.

All of these components have a “style” property, where we can define the css properties we want to style, such as font-size, colour, white-space, and many more. Most of the layout enhancements we want to achieve, can be achieved by defining the right css property, within the “style” of the Dash component. However, I faced different challenges in styling some features of these components, which seem inaccessible from their “style” properties. I’ve read similar challenges online from many different users, wondering how to style certain features or details of these components.

I think the main challenge here is to identify what’s the right CSS property that is used by Dash. There’s in fact a method to point and click, or “inspect” each element on the page, and unveil the CSS property it is defined by.

We can in-fact run the app in the browser, right-click on the page and choose “Inspect”: this will pop-up a window, on the right-side of the page, where we can navigate through the html and css code of the page. At the top of the page, we can click on this tool

Image by author

We can finally hover on the element we want to style and get all its CSS classes and properties to understand what to change / redefine.

4.2.1 Examples: style a DatePickerRange element

A DatePickerRange by default looks like this:

Image by author

We can change the component layout from the “style” property and define the font-size, add a border with some color, etc. But how to modify that arrow that separates the start date from the end date ? How to change the green colors highlighting the selected days or around the helper icon?

These elements seem unreachable from the “style” property and we have to use a different method to change their layout properties. Within the browser, if we inspect and select the arrow, we’ll see something like this:

Image by author

This component uses some CSS classes, that we can modify by adding some code into our custom CSS file, forcing the arrow to a new size:

Image by author

Similarly, we can unveil how the change the calendar style by inspecting the calendar selection:

Image by author

By analyzing this element, it seems that “.CalendarDay__selected” is the CSS class for the date extremes that are selected, while “.CalendarDay__selected_span” is the CSS class used for all the days in between the two extremes. It is possible to use these classes to change the background colors of the calendar (and to define some layout modifications when hovering the mouse on the different days). I’ve added the code below to the custom CSS file:

Image by author

(“!important” overrides any other layout definition for the same CSS element or class).

4.2.2 Example: style a dash_table.DataTable element

Another element which I found difficult to style was the DataTable that I used in the recap section at the top of the dashboard.

DataTables have, by default, a feature that enables to select a cell, making it appear in a pink/red background color. Additionally, by using the default CSS Bootstrap file, a default setting will change the background color to white for all the table rows we hover on.

Image by author

I wanted to really change these two default properties which were clashing with my overall layout.

First of all, the selectable cell is not actually a CSS property but a component property which can be disabled: adding “cell_selectable = False” within the creation of the DataTable removes this functionality.

In order to change the row hovering color, with the method explained before we can see that DataTable rows are actually <tr> </tr> html elements, with no particular CSS class associated.

Image by author

In order to customize the background color when hovering on rows, I’ve added the following on the custom CSS file:

Image by author

5. Callbacks & Components

Once the dashboard layout has been defined and the chart and filter components have been placed on the page, let’s move to the callbacks.

I’ll go through some examples of Callbacks, focusing on the most troublesome that I’ve used.

5.1 Multi dropdown filter: how to have a “Select All” option

Dashboards very often include one or more dropdown filters, allowing the user to select one or multiple values at the same time.

In very simple cases, there shouldn’t be any Callback “behind” the dropdown component: the user selects one or more dropdown options; the selected values are normally “inputs” for other callbacks that adjust what’s presented on charts.

However, in more complex cases, we may want to have a multi-dropdown selection, where the options of a second dropdown depend on the selections of the first dropdown. In the example that I’ve created, I have a country dropdown and a city dropdown: the city dropdown options, definitely depend on the country that has already been chosen.

To add some more complexity, I’ve noticed there is not an out-of-the-box feature, in Dash, to “Select All”. Especially in those cases where the dropdown options are long lists, it is highly recommended to add a way to “Select All”, otherwise we would need to enter one by one all the options every time we want to analyze all values. Additionally, I also wanted to configure the dashboard in a way that “no selection” (ie. leave the dropdown blank) means that all options are considered.

In order to achieve all the features described here, I’ve followed the steps listed right below.

5.1.1 Create options lists

First of all, I’ve created a list of options for each of the Dropdown components. The data that I randomly created has the “Country” information in one column, and the “City” information in another. I’ve simply created two unique lists, one for all possible cities and one for all possible countries and I’ve added a “(Select All)” entry, which I wanted to have at the top of the option list.

Next, I’ve included the Dropdown component on the page layout, simply specifying the “multi” property:

Image by author

5.1.2 Manage option lists dynamically

I’ve then defined a callback that takes as input the list of values (selections) from the first Dropdown and returns, as output, the list of options of the second Dropdown.

Given that the list of options depends on the values, I’ve generated a dictionary where each key is a possible value of the first dropdown, and each value is a list of all possible options of the second dropdown. The result looks like the following:

{Brasil : [Fortaleza, Rio De Janiero, Sao Paulo],

Italy : [Milan, Venice], … }

To finalize the Callback, some key considerations are:

  • the “values” from the first Dropdown will be a Python list
  • this list may or may not contain the “All” option
  • leaving the list of values empty, will mean that we’ll have [‘’] as input for our Callback

As a first step, I’ve created a variable (isselect_all), useful to determine if the user has left the first dropdown blank, or if the user chose “Select All”, or neither of these two possibilities.

In the first two cases, the callbacks simply return a sorted list of all possible cities. Otherwise, the user is picking one or more countries individually, therefore I’m using the previously created dictionary (that I named repo_groups_l1_l2) to calculate a sorted list of all possible cities.

Image by author

5.2 Unveiling seasonality patterns with Heatmaps

Among all possible charts, Heatmaps are ideal whenever we’d like to plot 3 dimensions and show seasonality or patterns in data. For instance, I’ve included a heatmap to show sales trends across the year (week number in my case) and day of the week, to see if there is any seasonality on these two dimensions.

The weekdays will be on the x-axis, the calendar week numbers will be on the y-axis; each cell will be colored depending on the sale volumes. The heatmap will also need to respond to filters (based on dates and on the city and country dropdowns).

5.2.1 Heatmap: the layout

First of all, let’s include a placeholder for the heatmap within the dash layout. In the below code, the heatmap will be in the last column with the id “sales-weekly-heatmap”:

Image by author

5.2.2 Heatmap: callback overview

Then, let’s move to the callback, which will have:

  • inputs: the different filters — In my case, these are the start and end date from the date picker and the “country” and “city” dropdown selections
  • output: the above dcc.Graph “figure” element
  • 1st section: data preparation. The callback will use the datasource (which consists of a single dataframe) and will filter and aggregate data by week nr. and weekday
  • 2nd section: graph preparation (using Plotly: go.Heatmap). The chart will use a predefined colorscale which I custom-created via a function

5.2.3 Heatmap: data preparation

In the data preparation part, I had to include some statements to handle the “Select All” for both the dropdown selections. In case the user has chosen this option from the dropdown, the dataframe doesn’t need to be filtered by country/city; otherwise, only the values selected by the user will be considered from the initial complete dataframe:

Image by author

I’ve then filtered the dataframe by start and end date and finally aggregated it by week number and weekday:

Image by author

5.2.4 Heatmap: graph configuration

In order to draw some nice and consistent charts, across the dashboard, I’ve created a go.Layout() variable (named corporate_layout) at the top of the callback file. This layout variable includes a lot of properties that are almost all the same across all dashboard charts (chart transparent background, font family, title font size, gridlines, zerolines, overall height, legend syle, margins, …).

Inside each chart callbacks, a new fig = go.Figure(data=data, layout=corporate_layout) is defined. This allows then to update the fig layout (via this command: fig.update_layout()) to adapt the corporate_layout and include the chart title and the axis titles.

With this approach, the main layout of the chart is defined only once: this helps keep the code clean, but also flexible; i.e. in order to change all charts height, we simply need to adjust one variable in the whole code.

Regarding the “data” component of the go.Figure, I’ve used a go.Heatmap(), including a colorscale, which I’ll describe right below.

Image by author

5.2.5 Heatmap: add a custom colorscale

Plotly heatmap includes some standard colorscales we can choose from, or we can assign a custom created one.

Plotly provides some instructions (available here) to create a custom colorscale. The colorscale that I’ve used looks like this: a list of 11 colors that are used by the heatmap according to the percentile of each datapoint.

Image by author

In order to generate colors in some gradient sequence, I’ve created a function that takes as input the number of colors we need to generate and the RGB values of two colors: one will be the “starting point” and the other will be the “target point”. The function, for each color, increments the RGB values of the same amount, reaching the target one. It is a very simplistic approach, but this function serves the purpose:

Image by author

We can finally add the colorscale to the Heatmap to unveil some seasonality: as we can see, sales seem to decrease between weeks 10 and 20; then they started to increase reaching again the same levels by week 25+. We don’t have a significant seasonality within the days of the week.

Image by author

5.3 Providing additional information when hovering on data points

Within Dash, hovertemplates can be defined to specify what data is shown in what format, when the user hovers the mouse on the datapoints.

It may be useful to include additional information in the hovertemplate, besides the data which is already used by the chart (x and y value, for instance). Plotly official documentation provides useful alternatives: I am going to present the one that I found most effective.

In the chart below, I’ve created a stacked bar-chart, where every country is represented by each vertical bar, showing the sales as the sum of each city's individual sales. When hovering, I also wanted to show the information about the total target as opposed to the sales:

Image by author

The “target” by city is an information present in the datasource, but it is something that is not shown in the chart: where is the hover-template taking this info?

The approach I followed is to basically create, within the dataframe used by the chart, a new column containing all the info I wanted to present in the hovertemplate:

Image by author

Plotly hovertemplate requires some html formatting: “<i> </i>” for instance means that the content will be italic; “<extra> </extra>” is used to remove the trace name of the datapoint. Values are formatted with the d3-format’s syntax (as explained by Plotly here): “{:,d}” is used to include thousand’s separators (in case you’re wondering how that would work for other value types: percentages with 2 decimals can be formatted like this: “{:.2%}”; dates can be formatted like: “{x|%a, %d-%b-%Y}”.

Once we have the hovertext within the data, we can use it in the chart, just by assigning the dataframe column to the chart hovertemplate.

6. How to deploy and share the dashboard

Dashboards created via Dash can be deployed as Flask applications.

[Oct, 2023 — Update] After Heroku’s cancellation of their free tier options, a valid alternative to make the application available online and for free is via PythonAnywhere.

Here are the steps that I followed to deploy the app shown in this post:

  • First, create a free account on PythonAnywhere. Before proceeding, make sure your app works on one of the Python versions that are pre-installed on PythonAnywhere (e.g. 3.9.0 — installing a new Python version on PythonAnywhere may require too much storage for the free tier).
  • Step 1: Create a new app in PythonAnywhere. Under the Web tab, create a new app, using Flask and select one of the available Python versions.
  • Step 2: Upload app files. Go to the “files” tab and in the default folder called “mysite”, recreate the same file/folder structure that we have locally. We may need to adapt some of the absolute paths: for instance, in all statements where we upload data from local files (.csv or other extensions), we need to use the PythonAnywhere environment path. For this tutorial, the path I needed to use is: “/home/DashDemo1/mysite/data/datasource.xlsx” (Note, this change is not needed for the .py modules we import).
  • Step 3: Install packages. Go to consoles, launch a bash console, and launch “pip list” to verify which Python packages need to be installed. Note: by running “pip install”, packages will be installed to Python 3.10 (the default version at the moment). If we selected a different version in step 1, we’ll need to specify what version we’re using. E.g., for Python3.9 use “pip3.9 list” or “pip3.9 install”.
  • Step 4: Configure the app launch command. Under the Web tab, make sure that the app is launched correctly in the “WSGI configuration file”. For this tutorial, I used the following statement “from index import server as application”.
  • Step 5: Load the app. As the very last step, reload the app by clicking on the green button under the Web tab.

The app can be reached on the URL that is displayed under the Web tab.

If any error message is displayed, we can access the error log, always within the Web tab.

7. Conclusions

Plotly Dash is definitely a fun and very powerful Python library: I have been looking for a quick way to get repeatable analysis automated and very well presented and this definitely served the purpose!

With Python knowledge, some html & css experience, I was able to realize this dashboard during spare time in few weeks: I must say that embodying CSS Bootstrap to the layout made the difference and definitely allowed to build an underlying “grid” that allows to control very easily every section of the dashboard in the way I wanted to be.

--

--

Constant Learner, passionate about data analytics, ML and data visualization. Interested in work, tech, music & guitar