images from https://plotly.com/examples/
In this part of the workshop, we'll
In Part 2, we'll
Reference: https://dash.plotly.com/
Can be taught in CS1/2
Low code $\Rightarrow$ accessible
Intuitive callback design
Quickly build web apps
Data science early in the curriculum
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
This should launch a browser-based instance of Visual Studio Code.
In the terminal, run
python3 -m pip install dash
Go ahead and install recommended Python extensions
#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)
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
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
#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)
app
is an object representing your Dash appapp.layout
defines how things are displayed on the pagechildren
where you can list any sub-componentsdcc
- Dash Core Components - Markdown (https://www.markdownguide.org/basic-syntax/), GUI components, figures, etc.<control>
key on your keyboard and hit the c
key.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.
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)
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
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.
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:
Radioitem
?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)
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:
Browse through the other components on the left side of the page to get an idea of what other options you have.
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:
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)
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
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)
Edit this example to make it your own
This can be a great way to make build cool apps around data processing, 2D arrays, dictionaries, etc.
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
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)