# Dash Concepts
**Recommended Leanring Resources**
https://app.datacamp.com/learn/courses/building-dashboards-with-dash-and-plotly
**Outline**
1. Dash Intro
2. Callback Functions in Plainwords
3. Chained Callback in Plain Words
4. Wombo Combo Jelly Swallow
**Color Notation**
:::warning
🤔 **Think**
:::
:::danger
‼️ **Important Concept**
:::
:::info
📝 **Case Study, Scenarios**
:::
---
## Dash Intro
:::warning
**Q: Is Dash a framework or a library?**
::::spoiler **🥸 Ans**
a Python fullstack **framework** built on top of Flask(backend), Plotly.js(visualization), and React.js(frontend)
::::
:::warning
**Q: What are the alternatives to Dash?**
::::spoiler **🥸 Ans**
ML related: Gradio(https://www.gradio.app/), Streamlit(https://streamlit.io/)
DS related: Panel(https://github.com/holoviz/panel), Flask(https://github.com/pallets/flask) + JS Frontend (e.g., React, Vue)
::::
---
## Callback Functions in a Figure
```mermaid
%%{init: {'theme': 'default'}}%%
C4Component
title Dash Callback Component Diagram
Container_Boundary(c1, "1. Initilize app & 5. Run app") {
Component(layout, "App", "Dash Core Components")
Container_Boundary(c2, "2. Layout") {
Component(dropdown, "Dropdown", "dcc.Dropdown", "User selects a country")
Component(graph, "Graph", "dcc.Graph", "Displays sales data as a bar chart")
Container_Boundary(c3, "3. Callback & 4. Update Function") {
Component(callback, "@callback", "Dash Callback", "Filters data and updates the graph")
Component(dataframe, "Data", "Pandas DataFrame", "Holds raw sales data")
Component(plotly, "px.bar", "Plotly Express", "Generates bar chart from filtered data")
}
}
}
Rel(layout, dropdown, "Contains")
Rel(layout, graph, "Contains")
Rel(dropdown, callback, "1. Triggers on change")
Rel(callback, dataframe, "2. Provides Input / Filters Data")
Rel(dataframe, plotly, "3. Generates chart")
Rel(plotly, callback,"4. Provides Output")
Rel(callback, graph,"5. Render Graph")
UpdateRelStyle(dropdown, callback, $offsetY="-50")
UpdateRelStyle(callback, dataframe, $offsetY="50")
UpdateRelStyle(dataframe, plotly, $offsetY="50")
UpdateRelStyle(plotly, callback, $offsetY="-80")
UpdateRelStyle(callback, graph, $offsetX="50", $offsetY="-50")
```
::::spoiler **code**
```python=
# 1. Initialize App
app = Dash()
# 2. Layout
app.layout = [
html.Img(src=logo_link, style={'margin': '30px 0px 0px 0px'}),
html.H1('Sales breakdowns'),
html.Div([
html.H2('Controls'),
html.Br(),
html.H3('Country Select'),
# Add a dropdown with identifier
dcc.Dropdown(
id='country_dd',
options=['United Kingdom', 'Germany', 'France', 'Australia', 'Hong Kong'],
style={'width': '200px', 'margin': '0 auto'})],
style={'width': '350px', 'height': '350px', 'display': 'inline-block', 'vertical-align': 'top', 'border': '1px solid black', 'padding': '20px'}),
html.Div([
dcc.Graph(id='major_cat'),
html.H2('Major Category', style={'border': '2px solid black', 'width': '200px', 'margin': '0 auto'})],
style={'width': '700px', 'display': 'inline-block'})
]
# 3. Callback
@callback(
# Set the input and output of the callback to link the dropdown to the graph
Output(component_id='major_cat', component_property='figure'),
Input(component_id='country_dd', component_property='value')
)
# 4. Update Function
def update_plot(input_country):
country_filter = 'All Countries'
sales = ecom_sales.copy(deep=True)
if input_country:
country_filter = input_country
sales = sales[sales['Country'] == country_filter]
ecom_bar_major_cat = sales.groupby('Major Category')['OrderValue'].agg('sum').reset_index(name='Total Sales ($)')
bar_fig_major_cat = px.bar(
title=f'Sales in {country_filter}', data_frame=ecom_bar_major_cat, x='Total Sales ($)', y='Major Category', color='Major Category',
color_discrete_map={'Clothes':'blue','Kitchen':'red','Garden':'green','Household':'yellow'})
return bar_fig_major_cat
# 5. Run app
if __name__ == '__main__':
app.run(debug=True)
```
::::
----
## Chained Callback in Plain Words
```mermaid
%%{init: {'theme': 'default'}}%%
C4Component
title Dash Chained Callback Component Diagram
Container_Boundary(c1, "1. Initilize app & 5. Run app") {
Component(app, "App", "Dash Core Components")
Container_Boundary(c2, "2. Layout") {
Component(dropdown, "Dropdown (Major)", "dcc.Dropdown")
Component(dropdown1, "Dropdown (Minor)", "dcc.Dropdown")
Component(graph, "Line Graph", "dcc.Graph", "Displays sales data as a line chart")
Container_Boundary(c3, "3. Callback & 4. Update Function") {
Component(callback, "@callback (major)", "Dash Callback", "Returns dictionary")
Component(callback1, "@callback (minor)", "Dash Callback", "Filters data and updates the graph")
Component(dataframe, "Data", "Pandas DataFrame", "Holds raw sales data")
Component(plotly, "px.bar", "Plotly Express", "Generates bar chart from filtered data")
}
}
}
Rel(app, dropdown, "Contains")
Rel(app, dropdown1, "Contains")
Rel(app, graph, "Contains")
Rel(dropdown, callback, "1. Triggers on change")
Rel(callback, dropdown1, "2. Provides dictionary")
Rel(dropdown1, callback1, "3. Provides Input")
Rel(callback1, dataframe, "4. Filters Data")
Rel(dataframe, plotly, "5. Generates chart")
Rel(plotly, callback1,"6. Provides Output")
Rel(callback1, graph,"7. Renders Graph")
UpdateRelStyle(callback1, dataframe, $offsetY="50")
UpdateRelStyle(dataframe, plotly, $offsetY="50")
UpdateRelStyle(plotly, callback1, $offsetY="-80")
UpdateRelStyle(callback1, graph, $offsetY="-70")
```
::::spoiler **code**
```python=
# 1. Initialize App
app = Dash()
# 2. Layout
app.layout = [
html.Img(src=logo_link, style={'margin':'30px 0px 0px 0px'}),
html.H1('Sales breakdowns'),
html.Div([
html.H2('Controls'),
html.Br(),
html.H3('Major Category Select'),
dcc.Dropdown(
id='major_cat_dd',
options=[{'label':category, 'value':category} for category in major_categories],
style={'width':'200px', 'margin':'0 auto'}),
html.Br(),
html.H3('Minor Category Select'),
dcc.Dropdown(id='minor_cat_dd', style={'width':'200px', 'margin':'0 auto'})],
style={'width':'350px', 'height':'350px', 'display':'inline-block',
'vertical-align':'top', 'border':'1px solid black', 'padding':'20px'}),
dcc.Graph(id='sales_line',style={'width':'700px', 'height':'650px','display':'inline-block'})
]
# 3-1. Callback
@callback(
Output('minor_cat_dd', 'options'),
Input('major_cat_dd', 'value'))
# 4-1. Function
def update_minor_dd(major_cat_dd):
major_minor = ecom_sales[['Major Category', 'Minor Category']].drop_duplicates()
relevant_minor_options = major_minor[major_minor['Major Category'] == major_cat_dd]['Minor Category'].values.tolist()
# Set up the Major Category options with the same label and value
formatted_relevant_minor_options = [{'label':option, 'value':option} for option in relevant_minor_options]
return formatted_relevant_minor_options
# 3-2. Callback
@callback(
Output('sales_line', 'figure'),
Input('minor_cat_dd', 'value'))
# 4-2. Function
def update_line(minor_cat):
minor_cat_title = 'All'
ecom_line = ecom_sales.copy()
if minor_cat:
minor_cat_title = minor_cat
ecom_line = ecom_line[ecom_line['Minor Category'] == minor_cat]
ecom_line = ecom_line.groupby('Year-Month')['OrderValue'].agg('sum').reset_index(name='Total Sales ($)')
line_graph = px.line(ecom_line, x='Year-Month', y='Total Sales ($)', title=f'Total Sales by Month for Minor Category: {minor_cat_title}')
return line_graph
# 5. Run app
if __name__ == '__main__':
app.run(debug=True)
```
::::
## Wombo Combo Jelly Swallow
### Requirement & Layout Mapping

### Layout & Code Mapping

### Relation & Callback Mapping


*Anotated Image Source: https://github.com/PoYaSharonLin/PDDS/blob/dash-concepts/Dash_Concepts.pptx*
::::spoiler **code**
```py=
from dash import Dash, dcc, html, Input, Output, callback
import plotly.express as px
import pandas as pd
from dash_ag_grid import AgGrid
ecom_sales = pd.read_csv('/usr/local/share/datasets/ecom_sales.csv')
logo_link = 'https://assets.datacamp.com/production/repositories/5893/datasets/fdbe0accd2581a0c505dab4b29ebb66cf72a1803/e-comlogo.png'
major_categories = list(ecom_sales['Major Category'].unique())
large_tb = ecom_sales.groupby(['Major Category', 'Minor Category'])['OrderValue'].agg(['sum', 'count', 'mean']).reset_index().rename(columns={'count':'Sales Volume', 'sum':'Total Sales ($)', 'mean':'Average Order Value ($)'})
ecom_country = ecom_sales.groupby('Country')['OrderValue'].agg('sum').reset_index(name='Total Sales ($)')
bar_fig_country = px.bar(ecom_country, x='Total Sales ($)', y='Country', width=500, height=450, title='Total Sales by Country (Hover to filter the Minor Category bar chart!)', custom_data=['Country'], color='Country', color_discrete_map={'United Kingdom':'lightblue', 'Germany':'orange', 'France':'darkblue', 'Australia':'green', 'Hong Kong':'red'})
money_fmt = {"function": ("params.value.toLocaleString('en-US', {style: 'currency', currency: 'USD'})")}
column_defs = [
{"field": "Major Category"},
{"field": "Total Sales ($)", "valueFormatter": money_fmt},
{"field": "Average Order Value ($)", "valueFormatter": money_fmt},
{"field": "Sales Volume"}
]
grid = AgGrid(
columnDefs=column_defs,
rowData=large_tb.to_dict("records"),
# Turn on pagination
dashGridOptions={
"pagination": True,
# Show 6 rows per page
"paginationPageSize": 6}
)
# 1. Initialize App
app = Dash()
# 2. Layout
app.layout = [
html.Img(src=logo_link, style={'margin':'30px 0px 0px 0px' }),
html.H1('Sales breakdowns'),
html.Div([
html.H2('Controls'),
html.Br(),
html.H3('Major Category Select'),
dcc.Dropdown(id='major_cat_dd',
options=[{'label':category, 'value':category} for category in major_categories],
style={'width':'200px', 'margin':'0 auto'}),
html.Br(),
html.H3('Minor Category Select'),
dcc.Dropdown(id='minor_cat_dd', style={'width':'200px', 'margin':'0 auto'})],
style={'width':'350px', 'height':'360px', 'display':'inline-block', 'vertical-align':'top', 'border':'1px solid black', 'padding':'20px'}),
html.Div([
html.H3(id='chosen_major_cat_title'),
dcc.Graph(id='sales_line')],
style={'width':'700px', 'height':'380px','display':'inline-block', 'margin-bottom':'5px'}),
# Insert the AG Grid
grid,
html.Div([
html.Div(dcc.Graph(id='major_cat', figure=bar_fig_country), style={'display':'inline-block'}),
html.Div(dcc.Graph(id='minor_cat'), style={'display':'inline-block'})],
style={'width':'1000px', 'height':'650px','display':'inline-block'})
]
# 3-1. Callback
@callback(
Output('minor_cat_dd', 'options'),
Output('chosen_major_cat_title', 'children'),
Input('major_cat_dd', 'value'))
# 4-1. Function
def update_dd(major_cat_dd):
major_minor = ecom_sales[['Major Category', 'Minor Category']].drop_duplicates()
relevant_minor = major_minor[major_minor['Major Category'] == major_cat_dd]['Minor Category'].values.tolist()
minor_options = [dict(label=x, value=x) for x in relevant_minor]
if not major_cat_dd:
major_cat_dd = 'ALL'
major_cat_title = f'This is in the Major Category of : {major_cat_dd}'
return minor_options, major_cat_title
# 3-2. Callback
@callback(
Output('sales_line', 'figure'),
Input('minor_cat_dd', 'value'))
# 4-2. Function
def update_line(minor_cat):
minor_cat_title = 'All'
ecom_line = ecom_sales.copy()
if minor_cat:
minor_cat_title = minor_cat
ecom_line = ecom_line[ecom_line['Minor Category'] == minor_cat]
ecom_line = ecom_line.groupby('Year-Month')['OrderValue'].agg('sum').reset_index(name='Total Sales ($)')
line_graph = px.line(ecom_line, x='Year-Month', y='Total Sales ($)', title=f'Total Sales by Month for Minor Category: {minor_cat_title}', height=350)
return line_graph
# 3-3. Callback
@callback(
Output('minor_cat', 'figure'),
Input('major_cat', 'hoverData'))
# 4-3. Function
def update_min_cat_hover(hoverData):
hover_country = 'Australia'
if hoverData:
hover_country = hoverData['points'][0]['customdata'][0]
minor_cat_df = ecom_sales[ecom_sales['Country'] == hover_country]
minor_cat_agg = minor_cat_df.groupby('Minor Category')['OrderValue'].agg('sum').reset_index(name='Total Sales ($)')
ecom_bar_minor_cat = px.bar(minor_cat_agg, x='Total Sales ($)', y='Minor Category', orientation='h', height=450, width=480,title=f'Sales by Minor Category for: {hover_country}')
ecom_bar_minor_cat.update_layout({'yaxis':{'dtick':1, 'categoryorder':'total ascending'}, 'title':{'x':0.5}})
return ecom_bar_minor_cat
# 5. Run app
if __name__ == '__main__':
app.run(debug=True)
```
::::