Reputation: 327
I am trying to update a plotly graph dash with two different dropdowns as inputs.
This is my sample dataframe:
import pandas as pd
df1 = {'category' : ['A','A','A','B','B','B'],'subcategory' : ['x', 'y', 'z', 'x1','y1','z1'],
'x_coord' : [1, 2,3,2,2,2],'y_coord' : [1,3,2,1,3,2]}
df_test = pd.DataFrame(df1)
df_test
And what I pretend to do is if I select category A, that plots in a scatter all the correspondent points to the category, but If Also I select a subcategory that modifies the graph plotting only the correspondent category-subcategory point of the dataframe.
The code is below, and it works if I only add the callback of the first dropdown, but when I add the second callback to the subcategory it doesn't work. I am following the suggestions in the dash plotly tutorial where it says:
A word of caution: it's not always a good idea to combine Outputs, even if you can: If the Outputs depend on some but not all of the same Inputs, keeping them separate can avoid unnecessary updates. If they have the same Inputs but do independent computations with these inputs, keeping the callbacks separate can allow them to run in parallel.
But anyway if I put the output in separate callbacks or in the same I cannot make it work, here is the code that I am trying (using jupyter notebook):
import dash
import plotly as py
import dash_core_components as dcc
import dash_html_components as html
from dash.dependencies import Input, Output
import plotly.graph_objects as go
from jupyter_plotly_dash import JupyterDash
py.offline.init_notebook_mode(connected = True)
app = JupyterDash('Test')
app.layout = html.Div([
dcc.Dropdown(id='dropdown1',
options=[{'label':i, 'value':i} for i in df_test['category'].unique()]),
dcc.Dropdown(id='dropdown2',
options=[{'label':i, 'value':i} for i in df_test['subcategory'].unique()]),
dcc.Graph(id='graphic')
])
@app.callback(
Output('dropdown2', 'options'),
[Input('dropdown1', 'value')])
def update_drop2(selected_drop):
filtered_df = df_test[(df_test.category == selected_drop)]
return [{'label':i, 'value':i} for i in filtered_df['subcategory'].unique()]
@app.callback(
Output('graphic', 'figure'),
[Input('dropdown1', 'value')])
def update_figure(selected_drop):
filtered_df = df_test[(df_test.category == selected_drop)]
fig = go.Figure()
fig.add_trace(go.Scatter(x=filtered_df.x_coord,y=filtered_df.y_coord, marker = dict(size=15, color='green'), mode='markers'))
return fig
@app.callback(
Output('graphic', 'figure'),
[Input('dropdown2', 'value')])
def update_figure(selected_drop):
filtered_df = df_test[(df_test.subcategory == selected_drop)]
fig = go.Figure()
fig.add_trace(go.Scatter(x=filtered_df.x_coord,y=filtered_df.y_coord, marker = dict(size=15, color='green'), mode='markers'))
return fig
app
If I use multiple inputs on the callback like this:
@app.callback(
Output('graphic', 'figure'),
[Input('dropdown1', 'value'), Input('dropdown2', 'value')])
def update_figure(selected_drop1, selected_drop2):
if not selected_drop2:
filtered_df = df_test[(df_test.category == selected_drop1)]
else:
filtered_df = df_test[(df_test.category == selected_drop1) &
(df_test.subcategory == selected_drop2)]
fig = go.Figure()
fig.add_trace(go.Scatter(x=filtered_df.x_coord,y=filtered_df.y_coord,
marker = dict(size=15, color='green'), mode='markers'))
return fig
It works better (or more near what I pretend), but however when I switch between categories I see no data.
Thanks in advance for your help and reccomendations.
Upvotes: 2
Views: 2680
Reputation: 13447
I had a similar problems the trick is to add to the second dropdown an option all
. Then I wanted on the second dropdown to show only the subcategories in the given category. So I actually use 2 callbacks for dropdowns and 1 callback for the plot.
app.py
import pandas as pd
import os
import plotly.graph_objs as go
import dash
import dash_core_components as dcc
import dash_html_components as html
from dash.dependencies import Input, Output
df = pd.DataFrame({'category' : ['A','A','A','B','B','B'],
'subcategory' : ['x', 'y', 'z', 'x1','y1','z1'],
'x_coord' : [1, 2,3,2,2,2],
'y_coord' : [1,3,2,1,3,2]})
# lists of categories
options1 = sorted(df["category"].unique().tolist())
# dictionary of category - subcategories
all_options = df.groupby("category")["subcategory"].unique()\
.apply(list).to_dict()
# we add as first subcategory for each category `all`
for k, v in all_options.items():
all_options[k].insert(0, 'all')
app = dash.Dash()
app.layout = html.Div([
dcc.Dropdown(
id='first-dropdown',
options=[{'label': k, 'value': k} for k in all_options.keys()],
value=options1[0]
),
html.Hr(),
dcc.Dropdown(id='second-dropdown'),
html.Hr(),
dcc.Graph(id='display-selected-values')
])
# the following two callbacks generate a dynamic 2 option
@app.callback(
dash.dependencies.Output('second-dropdown', 'options'),
[dash.dependencies.Input('first-dropdown', 'value')])
def set_2_options(first_option):
return [{'label': i, 'value': i} for i in all_options[first_option]]
@app.callback(
dash.dependencies.Output('second-dropdown', 'value'),
[dash.dependencies.Input('second-dropdown', 'options')])
def set_2_value(available_options):
return available_options[0]['value']
@app.callback(
dash.dependencies.Output('display-selected-values', 'figure'),
[dash.dependencies.Input('first-dropdown', 'value'),
dash.dependencies.Input('second-dropdown', 'value')])
def update_graph(selected_first, selected_second):
if selected_second == 'all':
ddf = df[df["category"]==selected_first]
else:
ddf = df[(df["category"]==selected_first) &
(df["subcategory"]==selected_second)]
fig = go.Figure()
fig.add_trace(
go.Scatter(x=ddf["x_coord"],
y=ddf["y_coord"],
marker = dict(size=15, color='green'),
mode='markers'))
return fig
if __name__ == '__main__':
app.run_server(debug=True, port=8051)
Upvotes: 1