Building Dashboards using Dash (< 200 lines of code) | by Rishav Agarwal | Towards Data Science

Dashboards are user interfaces (UIs) that visualize data in an organized manner. Business dashboards usually contain information around Key Performance Indicators (KPIs) related to particular objectives or business processes. A dashboard is a “snapshot” report that allows us to display data at a given instant of time in a meaningful manner with the aid of charts for easy reference and quick inference.
Some attributes of a useful dashboard are:
Customizable: An excellent dashboarding tool must allow users to customize according to need.
Accessible: Should be available in a variety of media formats like web, mobile etc. for viewing on the go.
Scalable: Should have the ability to add/change KPIs and add/change data sources.
Dashboards nowadays come in various shapes and sizes. Many companies roll out ready-made dashboarding services as SaaS. This software usually has workspaces where one can drag and drop data columns and KPIs. One of the most important dashboarding tools is Tabealu, which is self-contained software that allows users to build robust dashboards. However, if one might want to build their dashboarding tool, one would have to learn many technologies for visualization, database management and scripting. Below is a brief overview of some of the technologies involved (note this is not exhaustive):
Components of a dashboard Technologies
Visualization D3, React JavaScriptDatabase Management SQL, AWS, MongoDBScripting R-shiny, Python, Java, Cpp
Visualization D3, React JavaScript
Database Management SQL, AWS, MongoDB
Scripting R-shiny, Python, Java, Cpp
Using Python, we have several options at our disposal:
Dash: Dash is a powerful open-source library that helps build interactive and live web-based dashboards using Plotly, Flask and React.
Jupyter Dashboards: The dashboards layout extension is an add-on for Jupyter Notebook. It lets the user arrange notebook outputs in a grid or report like the format and saves this layout. The extension is required to be installed by other users to view this report.
Pyxley: Pyxlex is another excellent option for building dashboards. It leverages React and Flask. However, the support and documentation for this are limited.
Bokeh: A web dashboard tool that employs D3 and Tornado may require some knowledge of JavaScript.
Few more: Bowtie, Spyre, Superset
We go with Dash because:
Easy to use: Built on Plotly and React, so it is straightforward to code and has many widgets available. All you need to know is Python; no need to learn React or D3. However, if you know React, then Dash allows you to plug into React’s extensive ecosystem through an included toolset that packages React components into Dash-useable components.
Documentation: Dash is well documented and has a great and responsive community on Stack Overflow and Github.
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.
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.
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 backendpip install dash-renderer==0.13.2  # The dash front-endpip install dash-html-components==0.11.0  # HTML componentspip install dash-core-components==0.28.1  # Supercharged componentspip install dash_table_experiments #for tablespip 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.
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 genresHistogram 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
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.
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.
We will divide our dash component into three parts:
Data Manipulation (Model): Perform operation to read data from file and manipulate data for displayDashboard Layout (View): Visually render data on the web pageInteraction Between Components (Controller): Convert user input to commands for data manipulation and re-render.
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.
Here we import the relevant libraries.
import dashimport dash_core_components as dccimport dash_html_components as htmlimport dash_table_experiments as dtimport pandas as pdimport plotly.graph_objs as gofrom dash.dependencies import Input, Output, State, Eventimport random
Here we have the data in a csv so we can simply import it using pandas. However, if the data is in a SQLdatabase 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.
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.
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.yeardf['decade']=(df["year"]//10)*10#cleaning Genredf['genre'] = df['genre'].str.strip()df['genre'] = df['genre'].str.replace("/", ",")df['genre'] = df['genre'].str.split(",")#year trenddf_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#tabledf_table= df.groupby('artist').agg({'album':'size', 'metascore':'sum', 'userscore':'sum'})#genrebubbledf2=(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()
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.
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:
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"})
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 Chartdcc.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 tabledef  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 chartdef  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 layoutapp = 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 layoutapp.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.
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!
The following two lines need to be added to the code to run the app
if  __name__ == '__main__':app.run_server(debug=True)
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):
http://127.0.0.1:8050/
Our dashboard looks like:
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.
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.
Dash tutorial Dash core component Library Writing own components using dash Video tutorial on Dash