Dash Workshop: Part 1¶

Eric Manley, Drake University

What is Dash?¶

¶

images from https://plotly.com/examples/

  • Low-code Python web framework
  • Built on Flask, Plotly.js, and React.js
  • Don't need to know HTML or Javascript
  • Designed for data visualization dashboards

What are we covering¶

In this part of the workshop, we'll

  • Get everything installed
  • Build web apps with user interface components like input boxes, radio buttons, and dropdown menus
  • Discover how callbacks work, with several variations
  • Load data into our apps from a file

In Part 2, we'll

  • Show how to use Dash with Plotly data visualizations

Reference: https://dash.plotly.com/

Why should you use it in your class?¶

Can be taught in CS1/2

Low code $\Rightarrow$ accessible

Intuitive callback design

Quickly build web apps

Data science early in the curriculum

Getting Started¶

Should work with most IDEs and web browsers

Today we'll see how to set it up in Github Codespaces: https://github.com/features/codespaces

codespaces_splash.png

codespaces.png

Installing the Dash Module¶

This should launch a browser-based instance of Visual Studio Code.

In the terminal, run

python3 -m pip install dash

dashinstall.png

Running your first Dash App¶

Create a new file called dash_hello.py

Go ahead and install recommended Python extensions

python_extensions.png

Paste the following code into your file¶

In [ ]:
#these are the components we need from Dash
from dash import Dash, html, dcc

#this creates an object representing your application
#you should have this line for all Dash apps
app = Dash(__name__)

#the layout describes all of the pieces that display on the page
#the html.Div allows you to pass a list of things to display
app.layout = html.Div(children = [
    dcc.Markdown(
        children = 
            """
                ## Hello there!

                This is my __Dash__ web application.

                Isn't it _neat_?

            """
    ),

    dcc.Markdown(
        children = """
                    It can have multiple markdown cells.
                    * with
                    * bullets 
                    * even
                """
    )
])

#this will launch your application in a web server
#you should have this line for all Dash apps
if __name__ == '__main__':
    app.run_server(debug=True)

first_code.png

Viewing the App¶

This launches your app in a little development web server

If you're running it locally, just open up a web browser at http://127.0.0.1:8050/

In Codespaces, click Open in Browser

dash_running.png

In [ ]:
 

Exercise 1¶

Try adding a third markdown cell with your own message.

You shouldn't need to re-launch the app - it should detect the change in code and refresh as needed

running_app.png

Let's talk about the code¶

In [ ]:
#these are the components we need from Dash
from dash import Dash, html, dcc

#this creates an object representing your application
#you should have this line for all Dash apps
app = Dash(__name__)

#the layout describes all of the pieces that display on the page
#the html.Div allows you to pass a list of things to display
app.layout = html.Div(children = [
    dcc.Markdown(
        children = 
            """
                ## Hello there!

                This is my __Dash__ web application.

                Isn't it _neat_?

            """
    ),

    dcc.Markdown(
        children = """
                    It can have multiple markdown cells.
                    * with
                    * bullets 
                    * even
                """
    )
])

#this will launch your application in a web server
#you should have this line for all Dash apps
if __name__ == '__main__':
    app.run_server(debug=True)

Things to notice¶

  • app is an object representing your Dash app
  • app.layout defines how things are displayed on the page
    • hierarchical
    • each component has a parameter called children where you can list any sub-components
    • can use html wrapper objects,
    • dcc - Dash Core Components - Markdown (https://www.markdownguide.org/basic-syntax/), GUI components, figures, etc.
  • If you want to stop your Dash app, go back to the terminal, hold down your <control> key on your keyboard and hit the c key.

Input and Callbacks¶

The following code has an Input component which allows users to type into an input text box.

Notice that each component may also be given an id by assigning a value to the id parameter when creating that object. This will be useful for referring to this component in other parts of the code.

It also has a callback function which is defined to run any time the value in the Input textbox changes.

Notice the @app.callack decorator which describes the inputs (i.e., parameters) and outputs (i.e., returns) of the function.

Run this along with me as we discuss how it works.

In [ ]:
from dash import Dash, html, dcc
from dash.dependencies import Input, Output

app = Dash(__name__)

app.layout = html.Div(children = [
    dcc.Markdown(
        id = "name_prompt",
        children = "## Enter your name"
    ),

    dcc.Input(
        id = "name_input",
        value = "" #initially there is no value for the user input
    ),

    dcc.Markdown(
        id = "output_message",
        children = "" #initially the Markdown string is empty
    )
])

@app.callback(
    Output("output_message","children"),
    Input("name_input","value"),
)
def my_cool_message_generator(user_name):
    my_message = "Hello "+user_name+"!"
    return my_message

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

Specifying the Input¶

The Input part of the callback determines which component (the one with id "name_input") and parameter's (value) change triggers the function

This new value is passed to the function as an argument - user_name

callback_code_input.png

Specifying the Output¶

The Output part of the callback determines which component ("output_message") and parameter (children) should change as result of running this function.

The new value must be returned by the function.

callback_code_output.png

Exercise 2¶

The following code introduces a new kind of component - the Radioitem. Run this code and think-about/discuss what it is doing. Answer the following questions:

  • What is a Radioitem?
  • How many callback functions does this app have?
  • What causes each of the callback functions to run? Why?
  • What inputs and outputs do each callback function use?
In [ ]:
from dash import Dash, html, dcc
from dash.dependencies import Input, Output

app = Dash(__name__)

app.layout = html.Div(children = [
    dcc.Markdown(
        id = "name_prompt",
        children = "## Enter your name"
    ),

    dcc.Input(
        id = "name_input",
        value = "" #initially there is no value for the user input
    ),

    dcc.Markdown(
        id = "major_prompt",
        children = "What is your major?"
    ),

    dcc.RadioItems(
        id = "major_radio_items",
        options = ["Computer Science","Data Analytics","Artificial Intelligence","Other"],
        value = "Computer Science"
    ),

    dcc.Markdown(
        id = "name_output_message",
        children = "" #initially the Markdown string is empty
    ),

    dcc.Markdown(
        id = "major_output_message",
        children = "" #initially the Markdown string is empty
    )
])

@app.callback(
    Output("name_output_message","children"),
    Input("name_input","value"),
)
def my_cool_message_generator(user_name):
    my_message = "Hello "+user_name+"!"
    return my_message

@app.callback(
    Output("major_output_message","children"),
    Input("major_radio_items","value"),
)
def message_for_major(user_major):
    my_message = "Dash is great for "+user_major+" applications."
    return my_message

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

Exercise 3¶

Dropdown is another component that is similar to Radioitems. What do you think that it is supposed to do differently? In the code above, change the Radioitems to Dropdown and run it.

If time, also try changing it to Checklist. Discuss/think-about the error messages you get and how you might fix the code to get it to work as a checklist instead of radio items.

You can find documentation and examples on how to use each of these components here:

  • https://dash.plotly.com/dash-core-components/radioitems
  • https://dash.plotly.com/dash-core-components/dropdown
  • https://dash.plotly.com/dash-core-components/checklist

Browse through the other components on the left side of the page to get an idea of what other options you have.

Multiple Inputs affecting the same output¶

You can make multiple inputs affect the same output by listing multiple Input objects with a single callback function.

Notice how the inputs are related to the parameters in the example below:

In [ ]:
from dash import Dash, html, dcc
from dash.dependencies import Input, Output

app = Dash(__name__)

app.layout = html.Div(children = [
    dcc.Markdown(
        id = "name_prompt",
        children = "## Enter your name"
    ),

    dcc.Input(
        id = "name_input",
        value = "" #initially there is no value for the user input
    ),

    dcc.Markdown(
        id = "major_prompt",
        children = "What is your major?"
    ),

    dcc.RadioItems(
        id = "major_radio_items",
        options = ["Computer Science","Data Analytics","Artificial Intelligence","Other"],
        value = "Computer Science"
    ),

    dcc.Markdown(
        id = "output_message",
        children = "" #initially the Markdown string is empty
    )
])

@app.callback(
    Output("output_message","children"),
    Input("name_input","value"),
    Input("major_radio_items","value"),
)
def my_cool_message_generator(user_name,user_major): #the two params come from the two Input()
    if user_name == "": #the user hasn't entered a name yet
        return ""  #so return blank for the output message
    else:
        my_message = user_name + " is learning about " + user_major 
        return my_message


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

Loading and using other data with Dash applications¶

Most examples on the Dash documentation load data with Pandas

You don't have to teach Pandas to work with data

Try this example which loads the file HighestHolywoodGrossingMovies.csv which you can find on Kaggle

upload.png

In [ ]:
from dash import Dash, html, dcc
from dash.dependencies import Input, Output
import csv

app = Dash(__name__)

# reads data from a file into a 2d list
def data_prep(filename):
    with open(filename) as movie_file:
        data_reader = csv.reader(movie_file)
        data = []
        for row in data_reader:
            data.append(row)
        return data

# get a list of all the movie names
# - they appear in column 1 of the 2d list
def get_movie_names(data_2d_list):
    movie_names = []
    for row in data_2d_list[1:]:
        movie_names.append(row[1])
    return movie_names

# given a movie name, find and return the whole 
# row from the 2d list with the matching name
def search_movie_name(movie_name,data_2d_list):
    for row in data_2d_list:
        if row[1] == movie_name:
            return row
    return []

#A global variable. Be careful!
app_data = data_prep("HighestHolywoodGrossingMovies.csv")

app.layout = html.Div(children = [
    dcc.Markdown(
        id = "dropdown_prompt",
        children = "## Select a movie"
    ),

    dcc.Dropdown(
        id = "dropdown_items",
        options = get_movie_names(app_data),
        value = app_data[1][1]
    ),

    dcc.Markdown(id = "title_message"),
    dcc.Markdown(id = "synopsis_message"),
    dcc.Markdown(id = "sales_message"),

])

@app.callback(
    Output("title_message","children"),
    Output("synopsis_message","children"),
    Output("sales_message","children"),
    Input("dropdown_items","value"),
)
def dropdown_selection_(selected): 

    selected_movie_info = search_movie_name(selected,app_data)
    display_title = "**Title:** "+selected
    display_synopsis = "**Synopsis:** "+selected_movie_info[2]
    display_sales = "**World Sales:** "+selected_movie_info[7]
    return display_title,display_synopsis,display_sales


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

Exercise 4¶

Edit this example to make it your own

  • try displaying the data differently
  • use different data
  • include a new GUI component: https://dash.plotly.com/dash-core-components

Thoughts on using data¶

This can be a great way to make build cool apps around data processing, 2D arrays, dictionaries, etc.

Do I really have to use a global variable for the data?¶

  • it's easiest for students to get working
  • most examples in Dash documents do it
  • it's fine if you don't change the global variable
  • if you do change it, it will break with multiple users

Without the global variable¶

Here's a variation that stores the data in the user's browser using a dcc.Store() component inside app.layout

This value can now be shared accross callback functions

In [ ]:
from dash import Dash, html, dcc
from dash.dependencies import Input, Output
import csv

app = Dash(__name__)

def data_prep(filename):
    with open(filename) as movie_file:
        data_reader = csv.reader(movie_file)
        data = []
        for row in data_reader:
            data.append(row)
        return data

def get_movie_names(data_2d_list):
    movie_names = []
    for row in data_2d_list[1:]:
        movie_names.append(row[1])
    return movie_names

def search_movie_name(movie_name,data_2d_list):
    for row in data_2d_list:
        if row[1] == movie_name:
            return row
    return []

app.layout = html.Div(children = [
    dcc.Markdown(
        id = "dropdown_prompt",
        children = "## Select a movie"
    ),

    dcc.Dropdown(
        id = "dropdown_items",
        options = []
    ),

    dcc.Markdown(id = "title_message"),
    dcc.Markdown(id = "synopsis_message"),
    dcc.Markdown(id = "sales_message"),

    dcc.Store(id="browser_storage")
])

@app.callback(
    Output("title_message","children"),
    Output("synopsis_message","children"),
    Output("sales_message","children"),
    Input("dropdown_items","value"),
    Input("browser_storage","data")
)
def dropdown_selection_(selected,app_data): 
    
    if selected != None:
        selected_movie_info = search_movie_name(selected,app_data)
        display_title = "**Title:** "+str(selected)
        display_synopsis = "**Synopsis:** "+str(selected_movie_info[2])
        display_sales = "**World Sales:** "+str(selected_movie_info[7])
        return display_title,display_synopsis,display_sales
    else:
        return "","",""


@app.callback(
    Output("dropdown_items","options"),
    Output("browser_storage","data"),
    Input("dropdown_prompt","children"),
)
def initialize_data(dummy_component):
    app_data = data_prep("HighestHolywoodGrossingMovies.csv")
    movie_names = get_movie_names(app_data)
    return movie_names, app_data

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