How to refresh html map with dropdown menu in dash app? - python

I am new to python Dash. I am trying to create a dashboard with a dropdown menu and a map. By the selecting a value in the dropdown menu, the map will be updated with a new html map.
Here is the code:
dropdown_list = [{'label': 'MSFT', 'value': 'MSFT'},
{'label': 'IBM', 'value': 'IBM'}]
app.layout = html.Div(
children=[
html.Div(className='row',
children=[
html.Div(className='three columns div-user-controls',
children=[
html.Div(
children=[
dcc.Dropdown(id='selector', options=dropdown_list,
multi=False, value=dropdown_list[0]['value'],
style={'backgroundColor': '#1E1E1E'}
),
],
style={'color': '#1E1E1E'})
]
),
html.Div(className='nine columns div-for-charts bg-grey',
children=[
html.Iframe(id='map', style={'border': 'none'}, width='100%', height='750')
])
])
])
## Callback for timeseries price
#app.callback(Output('map', 'srcDoc'),
[Input('selector', 'value')])
def update_map(dropdown_values):
for vv in dropdown_values:
if vv == 'MSFT':
return open(html_file1, 'r').read()
elif vv == 'IBM':
return open(html_file2, 'r').read()
else:
return dash.no_update
The map, however, does not show up and is not updated. Can anyone give me some hints on this? Thank you in advance.

I have found the issue. In the dropdown menu, since multi=False, there will be only one value in the input of the callback function: dropdown_values. So there is no need to loop through a list and match the value.
Below is the corrected callback function
#app.callback(Output('map', 'srcDoc'),
[Input('selector', 'value')])
def update_map(dropdown_values):
if dropdown_values == 'MSFT':
return open(html_file1, 'r').read()
elif dropdown_values == 'IBM':
return open(html_file2, 'r').read()
else:
return dash.no_update

Related

Why Prevent_initial_call does not stop the initial call?

I have the written code below. I have two dropdown menus that work based on chained callbacks. the first dropdown menu gets the datasets and reads the columns' names and updates the options in the second dropdown menu. Then, the parameters can be plotted on the chart.
my dataframes look like this:
df={'col1':[12,15,25,33,26,33,39,17,28,25],
'col2':[35,33,37,36,36,26,31,21,15,29],
'col3':['A','A','A','A','B','B','B','B','B','B'],
'col4':[1,2,3,4,5,6,7,8,9,10]
I want to highlight the chart background depending on the categories in col3. I don't understand why when I select the dataset from the first dropdown menu the background color for col3 appears on the chart (before selecting the parameters). I have used Prevent_initial_call = True, but the second callback still triggers.
import dash
from dash import Dash, html, dcc, Output, Input, State, MATCH, ALL
import plotly.express as px
import pandas as pd
import numpy as np
import dash_bootstrap_components as dbc
app = Dash(__name__)
app.layout = html.Div([
html.Div(children=[
html.Button('add Chart', id='add-chart', n_clicks=0)
]),
html.Div(id='container', children=[])
])
#app.callback(
Output('container', 'children'),
[Input('add-chart', 'n_clicks'),
Input({'type': 'remove-btn', 'index': ALL}, 'n_clicks')],
[State('container', 'children')],
prevent_initial_call=True
)
def display_graphs(n_clicks, n, div_children):
ctx = dash.callback_context
triggered_id = ctx.triggered[0]['prop_id'].split('.')[0]
elm_in_div = len(div_children)
if triggered_id == 'add-chart':
new_child = html.Div(
id={'type': 'div-num', 'index': elm_in_div},
style={'width': '25%',
'display': 'inline-block',
'outline': 'none',
'padding': 5},
children=[
dbc.Container([
dbc.Row([
dbc.Col([dcc.Dropdown(id={'type': 'dataset-choice', 'index': n_clicks},
options=['dataset1'],
clearable=True,
value=[]
)], width=6),
dbc.Col([dcc.Dropdown(id={'type': 'feature-choice', 'index': n_clicks},
options=[],
multi=True,
clearable=True,
value=[]
)], width=6)
]),
dbc.Row([
dbc.Col([dcc.Graph(id={'type': 'dynamic-graph','index': n_clicks},
figure={}
)])
]),
dbc.Row([
dbc.Col([html.Button("Remove", id={'type': 'remove-btn', 'index': elm_in_div})
])
]),
])
]
)
div_children.append(new_child)
return div_children
if triggered_id != 'add-chart':
for idx, val in enumerate(n):
if val is not None:
del div_children[idx]
return div_children
#app.callback(
Output({'type': 'feature-choice', 'index': MATCH}, 'options'),
[Input({'type': 'dataset-choice', 'index': MATCH}, 'value')],
prevent_initial_call=True
)
def set_dataset_options(chosen_dataset):
if chosen_dataset is None:
return dash.no_update
else:
path = 'C:/Users/pymnb/OneDrive/Desktop/test/'
df = pd.read_csv(path + chosen_dataset+'.csv')
features = df.columns.values[0:2]
return features
#app.callback(
Output({'type': 'dynamic-graph', 'index': MATCH}, 'figure'),
[Input({'type': 'dataset-choice', 'index': MATCH}, 'value'),
Input({'type': 'feature-choice', 'index': MATCH}, 'value')],
prevent_initial_call=True
)
def update_graph(chosen_dataset1, chosen_feature):
if chosen_feature is None:
return dash.no_update
if chosen_dataset1 is None:
return dash.no_update
path = 'C:/Users/pymnb/OneDrive/Desktop/test/'
df = pd.read_csv(path + chosen_dataset1+'.csv')
Xmin = df[chosen_feature].min().min()
print(Xmin)
Xmax = df[chosen_feature].max().max()
# to find the height of y-axis(col4)
col4_max = df['col4'].max()
col4_min = df['col4'].min()
fig1 = px.line(df, x=chosen_feature, y='col4')
fig1.update_layout({'height': 600,
'legend': {'title': '', 'x': 0, 'y': 1.06, 'orientation': 'h'},
'margin': {'l': 0, 'r': 20, 't': 50, 'b': 0},
'paper_bgcolor': 'black',
'plot_bgcolor': 'white',
}
)
fig1.update_yaxes(range=[col4_max, col4_min], showgrid=False)
fig1.update_xaxes(showgrid=False)
categ_col3 = df.col3.dropna().unique()
colors = ['#54FF9F', '#87CEFF']
for (i,j) in zip(categ_col3, colors):
index_min = df.loc[df.col3 == i].index[0]
index_max = df.loc[df.col3 == i].index[-1]
if index_min == 0:
cat_min = df['col4'][index_min]
else:
cat_min = df['col4'][index_min-1]
cat_max = df['col4'][index_max]
fig1.add_shape(type="rect", x0=Xmin, y0=cat_min, x1=Xmax, y1=cat_max,
fillcolor=j, layer='below', opacity=0.5,
)
return fig1
if __name__ == '__main__':
app.run_server(debug=True)
You can fix it by modifying your code to the following:
#app.callback(
Output({'type': 'dynamic-graph', 'index': MATCH}, 'figure'),
[Input({'type': 'dataset-choice', 'index': MATCH}, 'value'),
Input({'type': 'feature-choice', 'index': MATCH}, 'value')],
prevent_initial_call=True
)
def update_graph(chosen_dataset1, chosen_feature):
if (chosen_feature == []) or (chosen_dataset1 is None): #<--- correct the condition
return dash.no_update
else: #<---- add the else condition to prevent any update
Xmin = df[chosen_feature].min().min()
Xmax = df[chosen_feature].max().max()
The reason behind that because all the elements are created on fly and they are not within the app.layout. Please read the following from the documentation:
In other words, if the output of the callback is already present in
the app layout before its input is inserted into the layout,
prevent_initial_call will not prevent its execution when the input is
first inserted into the layout.

Scatter plot clickdata returns multiple values (choices)

I’m trying to return name of points when clicking on Scatter Plot points but it just return one. How can I do to return multiple value when clicking multiple points. Below is my sample code:
from dash import Dash, html, dcc, Input, Output
import pandas as pd
import plotly.express as px
PCA_table=pd.read_excel('https://github.com/hoatranobita/Jobs/blob/main/PCA_table.xlsx?raw=true')
external_stylesheets = ['https://codepen.io/chriddyp/pen/bWLwgP.css']
app = Dash(__name__, external_stylesheets=external_stylesheets)
df = pd.read_csv('https://plotly.github.io/datasets/country_indicators.csv')
app.layout = html.Div([
html.Div([
html.Div([
dcc.Dropdown(
options=PCA_table.columns.unique(),
value='PC1',
id='crossfilter-xaxis-column',
)],style={'width': '49%', 'display': 'inline-block'}),
html.Div([
dcc.Dropdown(
options=PCA_table.columns.unique(),
value='PC2',
id='crossfilter-yaxis-column'
)], style={'width': '49%', 'float': 'right', 'display': 'inline-block'})
], style={'padding': '10px 5px'}),
html.Div([
dcc.Graph(
id='crossfilter-indicator-scatter',
clickData={'points': [{'hovertext': '184A1'}]}
)], style={'width': '49%', 'display': 'inline-block', 'padding': '0 20'}),
html.Div([
html.Div(id='x-time-series'),
], style={'display': 'inline-block', 'width': '49%'}),
])
#app.callback(
Output('crossfilter-indicator-scatter', 'figure'),
Input('crossfilter-xaxis-column', 'value'),
Input('crossfilter-yaxis-column', 'value'))
def update_graph(xaxis_column_name, yaxis_column_name):
fig = px.scatter(PCA_table,x=PCA_table[xaxis_column_name],
y=PCA_table[yaxis_column_name],
color=PCA_table['Labels'],
labels=PCA_table['Columns'],
hover_name=PCA_table['Columns'])
fig.update_layout(clickmode='event')
fig.update_layout(margin={'l': 40, 'b': 40, 't': 10, 'r': 0}, hovermode='closest')
return fig
#app.callback(
Output('x-time-series', 'children'),
Input('crossfilter-indicator-scatter', 'clickData'))
def update_y_timeseries(clickData):
click_name = clickData['points'][0]['hovertext']
return html.Span(click_name)
if __name__ == '__main__':
app.run_server(debug=False,port=1111)
As you see, I want to return click_name as multiple values not just one.
I’ve read about clickevent but still not get it. Thank you.
Use clickmode='event+select' to enable accumulation and to handle both click and selection events by listening for one single event (selectedData).
If layout.clickmode = 'event+select', selection data also accumulates
(or un-accumulates) selected data if you hold down the shift button
while clicking.
You can also set dragmode='select' if you want to modebar to be ready for selecting points (default mode is 'zoom').
fig.update_layout(
clickmode='event+select',
dragmode='select',
margin={'l': 40, 'b': 40, 't': 10, 'r': 0},
hovermode='closest'
)
Now in the callback, use selectedData instead of clickData :
#app.callback(
Output('x-time-series', 'children'),
Input('crossfilter-indicator-scatter', 'selectedData'),
prevent_initial_call=True)
def update_y_timeseries(selectedData):
selection= [p['hovertext'] for p in selectedData['points']]
return html.Span(', '.join(selection))

Manually update Dash table from user Input

I am trying to build a dashboard with Dash. One of the feature of this dashboard is that after dynamically updating dropdowns a user will be able to select multiple options and group them together.
I am able to group options together but when I go to group other options it rewrites my previous changes.
Such as when I group t1 and t2 in Group1 it is done but when I remove these selection from dropdown and select t3 and t4 in Group2 it rewrites my previous group.
Following is my code:
import dash
from dash.dependencies import Input, Output, State
import dash_core_components as dcc
import dash_html_components as html
import dash_bootstrap_components as dbc
import dash_table
import pandas as pd
data = [['tom', 'nick','t1',''], ['tom', 'nick','t2',''], ['tom', 'john','t3',''], ['tom','john','t4','']]
df = pd.DataFrame(data, columns = ['Captain', 'VCaptain', 'Teams','Groups'])
external_stylesheets = ['https://codepen.io/chriddyp/pen/bWLwgP.css']
app = dash.Dash(__name__, external_stylesheets=external_stylesheets)
server = app.server
colors = {
"graphBackground": "#F5F5F5",
"background": "#ffffff",
"text": "#000000"
}
app.layout = dbc.Container(
[
dbc.Row(
[
dbc.Col(dbc.Card([
dbc.FormGroup([
dbc.Label(html.B("Select Captains from list below:")),
dcc.Dropdown(id="captains",
options=[{'label': i, 'value': i} for i in df['Captain'].unique()],
value=[i for i in df['Captain'].unique()],)
]),
dbc.FormGroup([
dbc.Label(html.B("Select Vice Captains from below:")),
dcc.Dropdown(id="vcaptains")
]
),
dbc.FormGroup([
dbc.Label(html.B("Select Teams which needs to be grouped together:")),
dcc.Dropdown(id="teams",multi = True),
]
),
dbc.FormGroup([
dbc.Label(html.B("Input new group name:")),
dbc.Input(id="group", placeholder="Type group name...", type="text"),
]
),
html.I("Write a Name for the Group of the selected teams.",
style={"color":"#737373", "font-size":"0.8rem"}),
html.Br(),
dbc.Button("Add Group", id="assign", block=False,
outline=True, color="primary", #className = "mr-2",
style = {"margin-right": "10.5rem"}),
html.Br(),
], body = True,
style={"top": "20px",
"left":10,
"height":"900px",
"width": "20rem",
"padding": "2rem 1rem",
"background-color": "#f8f9fa",}
), width=2),
dbc.Col(width = 1),
dbc.Col(id='table',width=6, style={"top":-120})
],
align="center",
),
],fluid=True)
# # Dropdown to filter Captains
# #app.callback(
# Output('captains', 'options'),
# [Input('captains', 'value')])
# def captain_options(value):
# opts=[]
# if value is not None:
# opts = df['Captain'].unique()
# return [{'label': i, 'value': i} for i in opts]
# Dropdown to filter Vice Captains
#app.callback(
Output('vcaptains', 'options'),
[Input('captains', 'value')])
def vice_captain_options(value):
opts=[]
if value is not None:
opts = df[df['Captain'].isin([value])]['VCaptain'].unique()
return [{'label': i, 'value': i} for i in opts]
# Dropdown to filter Vice Captains
#app.callback(
Output('teams','options'),
[Input('captains', 'value'),
Input('vcaptains', 'value')])
def teams_options(captains, vcaptains):
opts=[]
if captains is not None:
opts = df[df['Captain'].isin([captains])]['Teams'].unique()
if vcaptains is not None:
opts = df[df['Captain'].isin([captains]) &
df['VCaptain'].isin([vcaptains])]['Teams'].unique()
return [{'label': i, 'value': i} for i in opts]
#app.callback(
Output('table', 'data'),
[Input('captains', 'value'),
Input('vcaptains', 'value')])
def table(captains, vcaptains):
df1 = df
if captains is not None:
df1 = df[(df['Captain'].isin([captains]))]
if vcaptains is not None:
df1 = df[(df['VCaptain'].isin([vcaptains]))]
if df1 is not None:
return df1.to_dict('records')
#app.callback(
Output('table', 'children'),
[Input('table', 'data'),
Input('assign', 'n_clicks')],
[State('teams', 'value'),
State('group', 'value')])
def table(data,n,teams,group):
df1 = pd.DataFrame.from_dict(data)
changed_id = [p['prop_id'] for p in dash.callback_context.triggered][0]
if 'assign' in changed_id:
if teams is not None:
if type(teams)==str:
df1.loc[df1['Teams'].isin([teams]), 'Groups'] = group
else:
df1.loc[df1['Teams'].isin(teams), 'Groups'] = group
if df is not None:
return dash_table.DataTable(data = df1.to_dict('records'),
columns=[{'id': c, 'name': c} for c in df1.columns])
if __name__ == '__main__':
app.run_server(debug=False)
If I've understood your problem correctly, then I think what you're missing is an intermediate step to hold data you've already selected. You could use the store component to do that. The flow would work like this:
Add selections to group 1
Group 1 selections update the Store
Add selections to group 2
Read in the existing Store value, and combine it with group 2 selections, then update the Store with the new combined values
Repeat until all desired selections are made
Something (perhaps a button) triggers the table to update using the data in the Store

Python: dashboard with multiple filters on the same Data Table

I have Dashboard which uses data from Pandas df. It is one table on which I want to provide 2 filters. One for COUNTRY, second for STATE. Below is a code (only problematic part shown) which I am using:
app.layout = html.Div(children=[
html.H4(children='STATISTICS FOR COUNTRY AND STATE'),
dcc.Dropdown(id='dropdown_country', options=[
{'label': i, 'value': i} for i in stats_df.COUNTRY.unique()
], multi=True, placeholder='Filter by COUNTRY...'),
dcc.Dropdown(id='dropdown_state', options=[
{'label': i, 'value': i} for i in stats_df.STATE.unique()
], multi=True, placeholder='Filter by STATE...'),
html.Div(id='table-container')
])
#app.callback(
dash.dependencies.Output('table-container', 'children'),
[dash.dependencies.Input('dropdown_country', 'value'), dash.dependencies.Input('dropdown_state', 'value')])
def display_table(dropdown_country, dropdown_state):
if dropdown_country is None and dropdown_state is None:
return generate_table(stats_df)
stats_dff = stats_df.loc[(stats_df.COUNTRY.str.contains('|'.join(dropdown_country))) | (stats_df.STATE.str.contains('|'.join(dropdown_state)))]
return generate_table(stats_dff)
With below code my Dashboard displays correctly but when I chose value for first filter i.e. COUNTRY it crashes with error:
Callback error updating table-container.children TypeError: can only
join an iterable
Can anyone help to point where is an error? Wit just one filter (COUNTRY or STATE) all works OK.
I figured it out. Function display_table needs to be changed to this:
def display_table(dropdown_country, dropdown_state):
if dropdown_country is None and dropdown_state is None:
return generate_table(stats_df)
if dropdown_country is not None and dropdown_state is not None:
stats_dff = stats_df.loc[(stats_df.COUNTRY.str.contains('|'.join(dropdown_country))) & (stats_df.STATE.str.contains('|'.join(dropdown_state)))]
return generate_table(stats_dff)
if dropdown_country is not None:
stats_dff = stats_df.loc[stats_df.COUNTRY.str.contains('|'.join(dropdown_country))]
return generate_table(stats_dff)
if dropdown_state is not None:
stats_dff = stats_df.loc[stats_df.STATE.str.contains('|'.join(dropdown_state))]
return generate_table(stats_dff)

Preventing a Dash dropdown menu from reseting to default value in multi-page app

I am currently trying out building a multi-page app in Python's Dash (changing a bit some of the materials from the tutorials) and I am wondering how you are able to prevent a dropdown menu in e.g. the first page from going back to the default value when you are returning to that page.
app.layout = html.Div([
dcc.Location(id='url', refresh=False),
html.Div(id='page-content'),
])
index_page = html.Div([
html.Br(),
dcc.Link('Go to Page 1', href='/page-1'),
html.Br(),
dcc.Link('Go to Page 2', href='/page-2'),
])
page_1_layout = html.Div([
dcc.Dropdown(
id='page-1-dropdown',
options=[{'label': i, 'value': i} for i in ['LA', 'NYC', 'MTL']],
value='LA'
),
html.Div(id='page-1-content'),
html.Br(),
dcc.Link('Go to Page 2', href='/page-2'),
html.Br(),
dcc.Link('Go back to home', href='/'),
])
page_2_layout = html.Div([
html.Div(id='page-2-content'),
html.Br(),
dcc.Link('Go to Page 1', href='/page-1'),
html.Br(),
dcc.Link('Go back to home', href='/')
])
#app.callback(Output('page-1-content', 'children'),
[Input('page-1-dropdown', 'value')])
def page_1_dropdown(value):
return html.Div([
html.Div(['You have selected "{}"'.format(value)]),#
])
# Update the index
#app.callback(Output('page-content', 'children'),
[Input('url', 'pathname')])
def display_page(pathname):
if pathname == '/page-1':
return page_1_layout
elif pathname == '/page-2':
return page_2_layout
else:
return index_page
# You could also return a 404 "URL not found" page here
if __name__ == '__main__':
app.run_server(debug=True)
In the example above, when I select e.g. 'NYC' from the dropdown menu, and then move to page 2 before moving back in pg 1, the dropdown selection has gone back to the default 'LA'.
It seems to be pretty straight-forward to prevent that, but I have not found a way to do it yet.
Thanks in advance!
You will need to use State for this. So record the event state for the drop down, so if it was clicked mark the state. And in your callback for page_1_dropdown check for the state and update to selected value. Something on the lines of :
#app.callback(Output('page-1-dropdown', 'value'),
[Input('page1_link', 'n_clicks')],
[State('page-1-dropdown', 'value')])
def drop_down(n_clicks, input1):
if (n_clicks) >=1 :
return(input1)
It should work by turning on persistence = True:
dcc.Dropdown(
id='page-1-dropdown',
options=[{'label': i, 'value': i} for i in ['LA', 'NYC', 'MTL']],
value='LA,
persistence = True
)
You can read more about this here: https://dash.plotly.com/persistence

Categories

Resources