Building Dashboards using Dash (< 200 lines of code)
By: Rishav Agarwal
Tags: Data Science, Tutorial, Dashboard, Data Visualization, Python

Dashboarding in MVC

Most UIs follow an MVC framework, by MVC, we mean Model-View-Controller. Each interconnected component is built to take on a specific task in the development process.

Model: The model is the heart of the dashboard. The model gets the data from the database, manipulates it and stores it in objects which can later be consumed by the view.

Controller: Controller is how the user interacts with the dashboard. It usually requests the data from the model and presents it to the view.

View: The view is where data is present to the user or the frontend. A view oversees the visual part of the dashboard.

The MVC framework reduces the application’s complexity and makes it easier to maintain; for example, the developer can choose to change the UI without needing to change any backed code. We will look at Dash from an MVC perspective for more fundamental understanding.

Figure 1 MVC diagram

Getting Started with Dash

As mentioned earlier, Dash is a simple Python tool that helps us build beautiful and responsive web dashboards quickly. It has support for both R and Python and is maintained by Plotly. It uses React for the Controller and View, and Plotly and Flask, for the Model in our MVC setting. We build a flask application that creates a dashboard on a web browser, which can call the backend to re-render certain parts of the web page.

Installation

Dash and Plotly can be installed very easily using pip . Using a virtual environment manager (like Conda ) is recommended.

pip install dash==0.26.3 # The core dash backend

pip install dash-renderer==0.13.2 # The dash front-end

pip install dash-html-components==0.11.0 # HTML components

pip install dash-core-components==0.28.1 # Supercharged components

pip install dash_table_experiments #for tables

pip install plotly --upgrade # Plotly graphing library

For the data manipulation, we install pandas .

pip install pandas

Please check the dash installation guide to get the latest version of these tools.

Now we move to the problem statement.

The problem statement

In a previous post, we had talked about building our own data set using Scrapy, that taught us how to use web scraping to download data. We had downloaded the top 500 albums of all time from the Metacritic.com library. Here is a snapshot of the dataset:

We see that we have a genre, artist, release date, meta-score and the user score for each album.

We will build a dashboard that has the following four components:

Interactive Bubble chart of genres Histogram of decade popularity of each genre (bar chart) Meta-score/User-score trends (line chart) Table of the top 10 most popular artist by meta score/user score

User can interact with the bar table with a drop-down to get top 10 artists by average meta score or average user score. User can also interact with the bubble chart; on hover, it will change the bar chart to give the number of albums of that genre published in each decade.

Mock-up

It is a good practice to build a small mock-up of the dashboard on paper or using a software (Adobe Illustrator and Balsamiq are great tools used by professionals).

Here we have used MS PowerPoint to build a simple static view.

Figure 2 Dashboard mockup

Dash in MVC framework

We will divide our dash component into three parts:

Data Manipulation (Model): Perform operation to read data from file and manipulate data for display Dashboard Layout (View): Visually render data on the web page Interaction Between Components (Controller): Convert user input to commands for data manipulation and re-render.

Initialization

Here we import the relevant libraries.

import dash

import dash_core_components as dcc

import dash_html_components as html

import dash_table_experiments as dt

import pandas as pd

import plotly.graph_objs as go

from dash.dependencies import Input, Output, State, Event

import random

Data Manipulation (Model)

Here we have the data in a csv so we can simply import it using pandas . However, if the data is in a SQL database or on a server somewhere we need to make a data connection that connects data to a pandas dataframe in our backend and the rest of the process will be the same.

Data manipulation with pandas

We assume that the reader has a basic knowledge about pandas and can understand the data manipulation. Basically, we are creating 4 major tables for each of the dashboard components:

df_linechart: This table groups the data by year and gives us the number of albums, average meta-score and average user score. We also multiply user score by 10 to get it on the same scale as meta-score. This will be used to draw the Score trends graph. df_table: This table groups the data by artist and gives us the number of albums, total meta-score and total user score. The generate_table function uses this table to get the top 10 rows sorted by user score or meta score. This is used to draw the table. df_bubble: This table groups the data by genre, and gives us the number of albums, mean meta-score and mean users score. The number of albums becomes the size of our bubble and the mean scores become the axes. This is used to draw the bubble chart. df2_decade: This table groups the data by genre and decade and returns the number of albums for each genre in each decade. This is used to draw the bar chart.

##############################################################

#DATA MANIPULATION (model)

############################################################## df= pd.read_csv("top500_albums_clean.csv")

df['userscore'] = df['userscore'].astype(float)

df['metascore'] = df['metascore'].astype(float)

df['releasedate']=pd.to_datetime(df['releasedate'], format='%b %d, %Y')

df['year']=df["releasedate"].dt.year

df['decade']=(df["year"]//10)*10

#cleaning Genre

df['genre'] = df['genre'].str.strip()

df['genre'] = df['genre'].str.replace("/", ",")

df['genre'] = df['genre'].str.split(",")

#year trend

df_linechart= df.groupby('year') .agg({'album':'size', 'metascore':'mean', 'userscore':'mean'}) .sort_values(['year'], ascending=[True]).reset_index()

df_linechart.userscore=df_linechart.userscore*10

#table

df_table= df.groupby('artist').agg({'album':'size', 'metascore':'sum', 'userscore':'sum'})

#genrebubble

df2=(df['genre'].apply(lambda x: pd.Series(x)) .stack().reset_index(level=1, drop=True).to_frame('genre').join(df[['year', 'decade', 'userscore', 'metascore']], how='left') )

df_bubble= df2.groupby('genre') .agg({'year':'size', 'metascore':'mean', 'userscore':'mean'}) .sort_values(['year'], ascending=[False]).reset_index().head(15)

df2_decade=df2.groupby(['genre', 'decade']).agg({'year':'size'}) .sort_values(['decade'], ascending=[False]).reset_index()

Dashboard Layout (View)

The layout determines how the dashboard would look after deployment. Dash provides Python classes for all the components. The components are saved in the dash_core_components and the dash_html_components library. One can also build their own components with JavaScript and React .

Introduction to Responsive Layout

We use a Bootstrap layout for our dashboard. Simply, Bootstrap standardizes the position of the components by containing them in a grid. It divides the screen into 12 columns and we can define as many rows as we like.

So our dashboard will look like:

Row

Column-12 (title)

Row

Column-6 (Table of most top 10 popular artist)

Column-6 (Meta-score/User-score trends- line chart) Row

Column-6 (Interactive Bubble chart of genres)

Column-6 (Histogram of decade popularity (bar chart)

Here is another visualization of the mock-up:

Figure 3 Mockup with Bootstrap grid

Adding Style

We can add custom CSS , to our dashboard by using .append.css command.

app = dash.Dash()

app.css.append_css({

"external_url": " https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css"

})

We also append the require JavaScript libraries for Bootstrap. Please refer to the Bootstrap page for latest versions:

# Bootstrap Javascript.

app.scripts.append_script({

"external_url": "https://code.jquery.com/jquery-3.2.1.slim.min.js"

})

app.scripts.append_script({

"external_url": "https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/js/bootstrap.min.js"

})

Layout Fundamentals

The layout skeleton can be defined as

html.Div(

[

SOME_DASH_OR_HTML_COMPONENT(

id,

content (data),

style (a dictionary with the properties))

], className (name of the Bootstrap Class)

)

the classname will be the Bootstrap class name which can be row or columns.

Dash makes it very easy to draw graphs thanks to the core components . We can use functions to draw components or we can write the code inline.

Adding table

We can either use the html.Table tag or import Plotly tables. Here we’re using a html native table.

def generate_table(dataframe, max_rows=10): '''Given dataframe, return template generated using Dash components

''' return html.Table(

# Header

[html.Tr([html.Th(col) for col in dataframe.columns])] +

# Body

[html.Tr([

html.Td(dataframe.iloc[i][col]) for col in dataframe.columns

]) for i in range(min(len(dataframe), max_rows))], style={'width': '100%', 'display': 'inline-block', 'vertical-align': 'middle'}

)

Simply, the function takes a pandas dataframe and coerces it to a table.

Graphs

Dash uses the Plotly graph layout. We define a graph inside a dcc.Graph component and define the graph inside a figure object (defined as go ). In this figure object we set the data, the style and layout. Each type of graph will have different relevant components to it. We discuss three types of graphs here:

Adding Line Graph

For the line graph we have to choose the Scatter type of graph with go.Scatter and in mode we define lines as shown below. We also need to define the data for x and y for each line.

In the layout section we can choose to display the legends, title and other style elements.

html.Div(

[ #Line Chart

dcc.Graph(id='line-graph',

figure=go.Figure(

data = [

go.Scatter(

x = df_linechart.year,

y = df_linechart.userscore,

mode = 'lines',

name = 'user score'

),

go.Scatter(

x = df_linechart.year,

y = df_linechart.metascore,

mode = 'lines',

name = 'meta score'

),

],

layout=go.Layout(title="Score trends")

)),], className = "col-md-6"

Adding bubble chart

Similarly, for a bubble chart, we define a go.Scatter but use mode = markers . We can also define a text component that gives the text on hovering over the marker. Later in our dynamic view will use this text to fill our fourth bar graph of “Decade Popularity”.

Markers are the points or bubbles on the graph, we can further customize the markers by passing a dictionary object with various options:

Color: A list of colors to be used for the markers , can be names of colors or a list of numbers for a color scale. Here I have passes a random list of numbers between 0 and 100.

Size: Size specifies the size of the bubble. Here we put the number of albums as the size. So a genre with more number of albums will have a larger bubble. With size we can further customize the size by passing the sizemode , sizeref and sizemin components.

html.Div([

dcc.Graph(id='bubble-chart',

figure=go.Figure(

data=[

go.Scatter(

x=df_bubble.userscore,

y=df_bubble.metascore,

mode='markers',

text=df_bubble.genre,

marker=dict(

color= random.sample(range(1,200),15),

size=df_bubble.year,

sizemode='area',

sizeref=2.*max(df_bubble.year)/(40.**2),

sizemin=4

))],

layout=go.Layout(title="Genre poularity")

))], className = "col-md-6"

),

Adding Bar Chart

Lastly, we draw a bar chart using a function. Note we could have directly pasted this part in the html div but since we want this graph to be interactive we use a function that can be used in a callback. We use go.Bar .

def bar(results):

gen =results["points"][0]["text"]

figure = go.Figure(

data=[

go.Bar(x=df2_decade[df2_decade.genre==gen].decade, y=df2_decade[df2_decade.genre==gen].year)

],

layout=go.Layout(

title="Decade popularity of " + gen

))

return figure

Here we have wireframed the app next we populate with data and add controls. Entire layout code:

#generate table

def generate_table(dataframe, max_rows=10):

'''Given dataframe, return template generated using Dash components

'''

return html.Table(

# Header

[html.Tr([html.Th(col) for col in dataframe.columns])] +

# Body

[html.Tr([html.Td(dataframe.iloc[i][col]) for col in dataframe.columns]) for i in range(min(len(dataframe), max_rows))], style={'width': '100%', 'display': 'inline-block', 'vertical-align': 'middle'}

) #generate bar chart

def bar(results):

gen =results["points"][0]["text"]

figure = go.Figure(

data=[

go.Bar(x=df2_decade[df2_decade.genre==gen].decade, y=df2_decade[df2_decade.genre==gen].year)

],

layout=go.Layout(

title="Decade popularity of " + gen

))

return figure # Set up Dashboard and create layout

app = dash.Dash()

# Bootstrap CSS.

app.css.append_css({

"external_url": "https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css"

})

# Bootstrap Javascript.

app.scripts.append_script({

"external_url": "https://code.jquery.com/jquery-3.2.1.slim.min.js"

})

app.scripts.append_script({

"external_url": "https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/js/bootstrap.min.js"

})

#define app layout

app.layout = html.Div([

html.Div([

html.Div([

html.H1("Music Dashboard", className="text-center", id="heading")

], className = "col-md-12"

),

],className="row"),

html.Div(

[ #dropdown and score

html.Div([

html.Div(

[

dcc.Dropdown(

options=[

{'label': 'userscore', 'value': 'userscore'},

{'label': 'metascore', 'value': 'metascore'},

],

id='score-dropdown'

)

], className="col-md-12"), html.Div(

html.Table(id='datatable', className = "table col-md-12")),

],className="col-md-6"),

html.Div(

[ #Line Chart

dcc.Graph(id='line-graph',

figure=go.Figure(

data = [

go.Scatter(

x = df_linechart.year,

y = df_linechart.userscore,

mode = 'lines',

name = 'user score'

),

go.Scatter(

x = df_linechart.year,

y = df_linechart.metascore,

mode = 'lines',

name = 'meta score'

),

],

layout=go.Layout(title="Score trends")

)

),

], className = "col-md-6"

),

], className="row"),

html.Div(

[

html.Div(

[

dcc.Graph(id='bubble-chart',

figure=go.Figure(

data=[

go.Scatter(

x=df_bubble.userscore,

y=df_bubble.metascore,

mode='markers',

text=df_bubble.genre,

marker=dict(

color= random.sample(range(1,200),15),

size=df_bubble.year,

sizemode='area',

sizeref=2.*max(df_bubble.year)/(40.**2),

sizemin=4

)

)

],

layout=go.Layout(title="Genre poularity")

)

)

], className = "col-md-6"

),

html.Div(

[

dcc.Graph(id='bar-chart',

style={'margin-top': '20'})

], className = "col-md-6"

),

], className="row"),

], className="container-fluid")

Don’t be scared by the size of the code. The code is formatted in this way for easier understanding. Note how each div component has a class defined along with it.

Interaction Between Components (Controller)

Now that we understand the layout let’s move to the controller. We have two interactive components in this dashboard one is the table that changes according to the type of score and the other is a bar chart that populates as per the selected genre bubble in out bubble chart.

Our controller skeleton will look like:

@app.callback(

Output(component_id='selector-id', component_property='figure'),

[

Input(component_id='input-selector-id',component_property='value')

]

)

def ctrl_func(input_selection)

Here we have 4 parts:

Callback: @app.callback is the callback function that hands the Input and Output. The Inputs and Outputs of our application are the properties of a particular component.

Input: this takes the id of the component uses as input and the property of that component we need to capture. This can be the value, the hoverData , clickData and so on.

Output: Output takes the id of the component which is to change and the property which will change typically this is either figure or children.

Control Function: cltr_function defines how the html for the Output will change.

Apart from these we also have State which allows us to add additional information apart from Input and Output.

Simply put, the app callback automatically captures any change made in the input and updates the output based on the cltr_function defines. We can have multiple inputs and multiple outputs in a single callback.

The code for callback in our case will look like:

##############################################################

#DATA CONTROL (CONTROLLER)

############################################################## @app.callback(

Output(component_id='datatable', component_property='children'),

[Input(component_id='score-dropdown', component_property='value')]

) def update_table(input_value):

return generate_table(df_table.sort_values([input_value], ascending=[False]).reset_index()) @app.callback(

Output(component_id='bar-chart', component_property='figure'),

[Input(component_id='bubble-chart', component_property='hoverData')]

) def update_graph(hoverData):

return bar(hoverData)

In the first callback, we give the input from the dropdown. The callback captures the value of the dropdown and passes it to the function update table which generates the given table. It then passes the table html data to the datatable component and we see the relevant table in out dashboard.

In the second callback, the data is passed is on hover from the bubble-chart component to the bar function. This data is a dictionary so we extract the relevant genre details using the text key and then pass the data to the bar function. The bar function subsets the df_decade data fro the given genre and plots a bar chart. Note how many different components are involved here and all it took was 10 lines!

Initialization

The following two lines need to be added to the code to run the app

if __name__ == '__main__': app.run_server(debug=True)

Running the app

The app can be run using:

python app.py

and we’re done!

You will see a message like this:

Serving Flask app “songsapp” (lazy loading)

Environment: production

WARNING: Do not use the development server in a production environment.

Use a production WSGI server instead.

Debug mode: on

Running on http://127.0.0.1:8050/ (Press CTRL+C to quit)

Restarting with stat

Debugger is active!

Debugger PIN: 282–940–165

The dashboard is available on the link (can change on execution refer to the link in your own code execution):

Our dashboard looks like:

Figure 4 Dashboard in Action

Next steps — Hosting

Finally, our dashboard is ready. The next step for this is to host the code on a server so that it accessible from any device. While hosting is easy, the steps involved require an in-depth understanding of hosting. Instead of burdening the reader with new concepts, we leave her with quick links to some hosting tutorials. This can be of use to both seasoned developers and freshers.

Some popular hosting options are:

Heroku: Dash has a short tutorial about hosting apps on Heroku. Alternatively, here is the tutorial by Heroku for deploying python apps.

AWS: Amazons’ tutorial on deploying python apps.

Conclusion

So this is how we build a simple yet interactive dashboard in < 200 lines of code. To really understand the real power of dash, do check out the dash gallery and the amazing stuff people have done with it. The entire code for this post can be found in this git repo.

Helpful resources

Dash tutorial

Dash core component Library

Writing own components using dash

Video tutorial on Dash