How to make a plot.ly dash app

Prerequisites:

  • Some knowledge of HTML/CSS
  • Python
  • plot.ly

Follow the instructions here to install dash: https://dash.plotly.com/installation

Our dash app has 2 main parts:

  • Layout – the way the app looks
  • Callback functions – how we interact with the app
import dash
import dash_html_components as html

external_stylesheets = ['https://codepen.io/chriddyp/pen/bWLwgP.css']

app = dash.Dash(__name__, external_stylesheets=external_stylesheets)
server = app.server

app.layout = html.H3('This is a dashboard')

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

We need to import html components to create the layout of the app. HTML components work just like pure HTML – there is a children-parent relationship between components and it is represented as a python list within a component.

We use the external style sheets to avoid creating our own .css file. However, it is easy to implement custom CSS on top of the external stylesheet. The external stylesheet helps with organising the components on the page. The page is divided into 12 columns from which you can choose to have one component as a full row or split the row in half using the six columns class for both components etc.

The code above will produce a working app with only a couple lines of code but it doesn’t do much yet. We need to add some data and most importantly – we need to interact with the app.

In order to make an interactive web app we need to import dash core components, plotly graph objects and input/output for the callback functions. And of course pandas for dataframe manipulation.

I will be using data from the World Happiness Report 2021 which can be found here https://worldhappiness.report/ed/2021/#appendices-and-data and at the end we will have a map of the world with some key statistics.

We read in the csv file and start adding some components to the app. I want to have a map of the world, a dropdown to select a metric and a slider to adjust the year we are viewing the statistics for. Thankfully, plotly dash comes with all of these so let’s use them. For all components we want to interact with, we need a component id. For the dropdown we need the options to select – the labels are what we see on the front end and the values are what we will use in the callback functions.

dcc.Dropdown(
                    id='map-dropdown',
                    options=[
                        {'label': 'Life Ladder', 'value': 'ladder'},
                        {'label': 'Log GDP per capita', 'value': 'gdp'},
                        {'label': 'Social Support', 'value': 'social'},
                        {'label': 'Healthy life expectancy at birth', 'value': 'exp'},
                        {'label': 'Freedom to make life choices', 'value': 'free'}
                    ],
                    value='ladder'
                )

The slider needs a minimum and a maximum value and tick marks to be visible on the app.

dcc.Slider(
                    id='map-year',
                    min=min(year_list),
                    max=max(year_list),
                    marks={f'{year}': {'label': f'{year}'} for year in year_list},
                    value=min(year_list)
                )

Finally for the map we only need a Graph component with an id through which we will feed in the figure later.

dcc.Graph(
	         id='map'
          )

Wrap it all in an html.Div and the layout is complete!

# The Layout
html.Div(
            [
                dcc.Dropdown(
                    id='map-dropdown',
                    options=[
                        {'label': 'Life Ladder', 'value': 'ladder'},
                        {'label': 'Log GDP per capita', 'value': 'gdp'},
                        {'label': 'Social Support', 'value': 'social'},
                        {'label': 'Healthy life expectancy at birth', 'value': 'exp'},
                        {'label': 'Freedom to make life choices', 'value': 'free'}
                    ],
                    value='ladder'
                ),
                dcc.Slider(
                    id='map-year',
                    min=min(year_list),
                    max=max(year_list),
                    marks={f'{year}': {'label': f'{year}'} for year in year_list},
                    value=min(year_list)
                ),
                dcc.Graph(
                    id='map'
                )
            ]
        )

Callback functions can take multiple components as input and output. For this app we only need one output which is the map. We need to provide the component id and the component property – for the output this is the figure property of the Graph component. As inputs, we need the value of the Dropdown component and the value of the Slider component. These inputs will be the parameters for the function we define below the app.callback statement in the order they are given.

# The Callback
@app.callback(
    Output('map', 'figure'),
    Input('map-dropdown', 'value'),
    Input('map-year', 'value')
)

Finally we write the function. Set the date to the selected date on the Slider to filter the dataframe then multiple if statements for the value of the Dropdown to create the map. I am using the go.Choropleth plot, making use of the built in ISO alpha-3 codes for countries as the locations property. The z property will decide the metric by which the map is coloured. The text propery is set to the name of the country which will be visible when the user hovers over the map.

# The Function
def update_map(value_dd, value_y):
    dff = df[df['year'] == value_y]
    fig = go.Figure()
    if value_dd == 'ladder':
        fig.add_trace(go.Choropleth(
            locations=dff['code'],
            z=dff['Life Ladder'],
            colorscale='Reds',
            marker_line_color='white',
            text=dff['Country name']
        ))
		...

And lastly we need to use plotly’s update_layout function to make the map look a bit nicer!

Click run and ta-da! You made an interactive dashboard with only a few lines of code.

Dropdown:

Hover text:

Slider:

Full code:

import dash
import dash_html_components as html
import dash_core_components as dcc
from dash.dependencies import Input, Output
import plotly.graph_objects as go
import pandas as pd

external_stylesheets = ['https://codepen.io/chriddyp/pen/bWLwgP.css']

app = dash.Dash(__name__, external_stylesheets=external_stylesheets)
server = app.server

df = pd.read_csv('happiness.csv')
year_list = sorted(list(df.year.unique()))

app.layout = html.Div(
    [
        html.H3('World Happiness', style = {'text-align':'center'}),
        html.Div(
            [
                dcc.Dropdown(
                    id='map-dropdown',
                    options=[
                        {'label': 'Life Ladder', 'value': 'ladder'},
                        {'label': 'Log GDP per capita', 'value': 'gdp'},
                        {'label': 'Social Support', 'value': 'social'},
                        {'label': 'Healthy life expectancy at birth', 'value': 'exp'},
                        {'label': 'Freedom to make life choices', 'value': 'free'}
                    ],
                    value='ladder'
                ),
                dcc.Slider(
                    id='map-year',
                    min=min(year_list),
                    max=max(year_list),
                    marks={f'{year}': {'label': f'{year}'} for year in year_list},
                    value=min(year_list)
                ),
                dcc.Graph(
                    id='map'
                )
            ]
        )
    ]

)


@app.callback(
    Output('map', 'figure'),
    Input('map-dropdown', 'value'),
    Input('map-year', 'value')
)
def update_map(value_dd, value_y):
    dff = df[df['year'] == value_y]
    fig = go.Figure()
    if value_dd == 'ladder':
        fig.add_trace(go.Choropleth(
            locations=dff['code'],
            z=dff['Life Ladder'],
            colorscale='Reds',
            marker_line_color='white',
            text=dff['Country name']
        ))
    if value_dd == 'gdp':
        fig.add_trace(go.Choropleth(
            locations=dff['code'],
            z=dff['Log GDP per capita'],
            colorscale='Blues',
            marker_line_color='white',
            text=dff['Country name']
        ))
    if value_dd == 'social':
        fig.add_trace(go.Choropleth(
            locations=dff['code'],
            z=dff['Social support'],
            colorscale='Greens',
            marker_line_color='white',
            text=dff['Country name']
        ))
    if value_dd == 'exp':
        fig.add_trace(go.Choropleth(
            locations=dff['code'],
            z=dff['Healthy life expectancy at birth'],
            colorscale='Oranges',
            marker_line_color='white',
            text=dff['Country name']
        ))
    if value_dd == 'free':
        fig.add_trace(go.Choropleth(
            locations=dff['code'],
            z=dff['Freedom to make life choices'],
            colorscale='Purples',
            marker_line_color='white',
            text=dff['Country name']
        ))
    fig.update_layout(
        geo=dict(
            showframe=False,
            showcoastlines=False
        ),
        autosize=True,
        margin=dict(t=0, b=0, l=0, r=0)
    )
    return fig


if __name__ == '__main__':
    app.run_server(debug=True)
By Zeynep Bicer
Published
Categorized as blog

Leave a comment

Your email address will not be published. Required fields are marked *