0
votes

I'm trying to create a dependent dropdown in dash/plotly, based on the unique values from the column depending on what is selected in the first dropdown. I created a chained callback and visually, the graph is updating correctly and the dropdowns are populating correctly. However, I receive an KeyError for one of my fields (Category, which has nothing to do with the dropdowns, it simply colors the categories), after a fair amount of tinkering I still can't figure out what's causing it. Here's my layout, callbacks and the error I'm getting in the debug environment:

    #Layout
    app.layout = html.Div(children=[
    
    dcc.Dropdown(id='select_game',
                 options=[
                     {"label": "Harry Potter and the Philosopher's Stone (HP1)", "value":"HP1"},
                     {"label": "Harry Potter and the Chamber of Secrets (HP2)", "value":"HP2"},
                     {"label": "Harry Potter and the Prisoner of Azkaban (HP3)", "value":"HP3"},
                     {"label": "Harry Potter and the Goblet of Fire (HP4)", "value":"HP4"}
                     ],
                 multi=False,
                 value="HP1",
                 clearable=False, className = 'dcc-compon'
        
        ),

    dcc.Dropdown(id='select_platform',
                 options=[],
                 multi=False,
                 clearable=False, className = 'dcc-compon'
        
        ),    
    html.Br(),
    dcc.Graph(id="wrprogvis", figure={}),
    
], style={'font-family':'bahnschrift'})

#Chained callback
@app.callback(
    Output('select_platform', 'options'),
    Input('select_game', 'value')
    )

def get_platforms(select_game):
    wrcombo_plts = wrcombo[wrcombo['Game']==select_game]
    return [{'label': i, 'value': i} for i in wrcombo_plts['Platform'].unique()]
    print(wrcombo_plts['Platform'].unique())

@app.callback(
    Output('select_platform', 'value'),
    Input('select_platform', 'options')
    )

def slct_platform(select_platform):
    return[k['value'] for k in select_platform][0]

@app.callback(
        Output(component_id='wrprogvis', component_property='figure'),
        [Input(component_id='select_game', component_property='value'),
         Input(component_id='select_platform', component_property='value')
         ]
       )

#Graph build
def update_graph(game_slct, plat_slct):
    
    wrcombo_ = wrcombo.copy()
    wrcombo_ = wrcombo_[wrcombo_['Game']==game_slct]
    wrcombo_ = wrcombo_[wrcombo_['Platform']==plat_slct]
    
    wrfig = px.line(wrcombo_, x='Date', y='Time', color='Category', custom_data=['Runner', 'Game', 'Notes'], height=600)
    wrfig.update_traces(
        mode="markers+lines", 
        hovertemplate='<b>%{customdata[0]}</b><br>Time: %{y|%H:%M:%S} <br>Achieved on: %{x|%e %b %Y} <br>Notes: %{customdata[2]}'
        )
    
    wrfig.update_layout(yaxis_tickformat='%H:%M', title_font={"family": "Bahnschrift"}, legend_font_family="Bahnschrift", legend_title_font_family="Bahnschrift")
    wrfig.update_xaxes(
            title_font = {"family": "Bahnschrift"},
            tickfont = {"family": "Bahnschrift"},
            showspikes=True
        )
    
    wrfig.update_yaxes(
            title_font = {"family": "Bahnschrift"},
            tickfont = {"family": "Bahnschrift"},
            showspikes=True,
    )

    return wrfig

Error:

KeyError: 'Category'
Traceback (most recent call last)

    File "HPWRprog.py", line 194, in update_graph

    wrfig = px.line(wrcombo_, x='Date', y='Time', color='Category', custom_data=['Runner', 'Game', 'Notes'], height=600)

    File "_chart_types.py", line 252, in line

    return make_figure(args=locals(), constructor=go.Scatter)

    File "_core.py", line 1889, in make_figure

    for val in sorted_group_values[m.grouper]:

    KeyError: 'Category'

Traceback (most recent call last):
  File "HPWRprog.py", line 194, in update_graph
    wrfig = px.line(wrcombo_, x='Date', y='Time', color='Category', custom_data=['Runner', 'Game', 'Notes'], height=600)
  File "_chart_types.py", line 252, in line
    return make_figure(args=locals(), constructor=go.Scatter)
  File "_core.py", line 1889, in make_figure
    for val in sorted_group_values[m.grouper]:
KeyError: 'Category'

Appreciate any help in advance!

edit: callback graph is showing an unwanted callback, I don't want graph update to be triggered until the options are updated (i.e. the path on the right). is there any way I can prevent this? callback graph

1
It looks like the wrcombo_ data frame does not have a Category column. Can you inspect it just before that line to see what it does have?coralvanda
@coralvanda how do I print the table from the callback? I tried adding print(wrcombo_) and it does not give me any result in my python console when I trigger the callback. Either way, I double checked the wrcombo table and it definitely has Category, wrcombo_ is simply a copyartfulinfo
Put the print before the return. I see you have them in the wrong order in get_platformscoralvanda
@coralvanda I tried moving the print statement and didn't have any luck - but when I manually filter the options from dropdowns in wrcombo it works. I had a thought though, I checked the callback graph and it seems that it has 2 paths (see edit on the main post). The path where it goes straight to update wrprogvis from select_game should not be happening, it should always update the options first, otherwise the options for `select_platform' may not be correct. Have I missed something with the sequencing of my callbacks?artfulinfo

1 Answers

1
votes

So I have a workaround, I'm sure there is a better solution but this is working as expected. I believe the graph update_graph function is being triggered after the game dropdown is updated, but before the options are updated, resulting in the filter on wrcombo_ producing a blank table. After the 2nd callback, the error was fixing itself as the platform is updated and the table can filter correctly. Introducing a check if the table is empty allows me to decide whether or not to refresh the graph, as below:

def update_graph(game_slct, plat_slct):

wrcombo_ = wrcombo.copy()
wrcombo_ = wrcombo_[wrcombo_['Game']==game_slct]
wrcombo_ = wrcombo_[wrcombo_['Platform']==plat_slct]

if len(wrcombo_) != 0:
    print(wrcombo_)
    wrfig = px.line(wrcombo_, x='Date', y='Time', color='Category', custom_data=['Runner', 'Game', 'Notes', 'Category'], height=600)
    wrfig.update_traces(
        mode="markers+lines", 
        hovertemplate='<b>%{customdata[0]}</b><br>Time: %{y|%H:%M:%S} <br>Achieved on: %{x|%e %b %Y} <br>Notes: %{customdata[2]}'
        )

    return wrfig
else:
    return dash.no_update

As I mentioned, not sure this is optimal, as I am still having to filter the table first - so please let me know if there is a better way to do this. There is no need for the 3rd callback to trigger until the second one has, so disabling that is probably the missing piece of the puzzle.