Chained Callback Error in a dcc.Tab

Hi, I am new to Dash. I created a Dash with 2 tabs, continent and country. In the country tab, I want to create a chained callback for the country dropdown by filtering continent. However, I received a ValueError: cannot assign without a target object. I have read through Dash App With Chained Callbacks, and there is no issue when I run the chained callback without inserting it into dcc.Tab. Please help as any advice is greatly appreciated.

My code:

app = Dash(__name__)

tab1 = html.Div([
    html.H2("Select Continent(s)"),
    dcc.Dropdown(
        id="continent_dropdown",
        options=sorted(df["Continent"].unique()),
        multi=True,
        searchable=True
    ),
    html.Br(),
    html.Div("Geospatial Data (by Continent)"),
    html.Br(),
    dcc.Loading(dcc.Graph(id="graph1"), type="cube")
])

tab2 = html.Div([
    html.H2("Select Country(ies)"),
    dcc.Dropdown(
        id="country_dropdown",
        options=sorted(df["Country"].unique()),
        multi=True,
        searchable=True
    ),
    html.H4("Filter by Continent"),
    dcc.Dropdown(
        id="continent_filter_dropdown",
        options=sorted(df["Continent"].unique()),
        multi=True,
        searchable=True
    ),
    html.Br(),
    html.Div("Geospatial Data (by Country)"),
    html.Br(),
    dcc.Loading(dcc.Graph(id="graph2"), type="cube")
])


def choropleth_map(data):
    fig = px.choropleth(data, #remaining choropleth code......)
    return fig


app.layout = html.Div([
    dcc.Tabs(
        id="tabs",
        value="continent",
        children=[
            dcc.Tab(
                label="Continent",
                value="continent",
                children=tab1
            ),
            dcc.Tab(
                label="Country",
                value="country",
                children=tab2
            )
            ]
    )
    ])


@app.callback(
    Output("graph1", "figure"),
    Input("continent_dropdown", "value")
)
def display_continent(continent_value):
    dff = copy.deepcopy(df)

    if continent_value:
        dff = df[df["Continent"].isin(continent_value)]

    return choropleth_map(dff)


@app.callback(
    Output("country_dropdown", "options"),
    Input("continent_filter_dropdown", "value")
)
def set_country_options(continent_value):
    dff = copy.deepcopy(df)

    if continent_value is not None:
        dff = dff.query("Continent = @continent")
    return sorted(dff["Country"].unique())


@app.callback(
    Output("graph2", "figure"),
    Input("country_dropdown", "value")
)
def display_country(country_value):
    dff = copy.deepcopy(df)

    if country_value:
        dff = df[df["Country"].isin(country_value)]

    return choropleth_map(dff)


if __name__ == "__main__":
    app.run(debug=True)

Welcome to the community @zeroeric2020 !!

I guess the problem is with the pandas.Dataframe.query() function. I am not familiar with this function but it is likely that you wont query lists like that, you need to check how to correctly query list objects if you want to stick with using it.

Even it is the correct usage, you used dff.query("Continent = @continent") . Clearly, you need to change continent to continent_value.

On the other hand, you can easily transform your code like that:

from dash import Dash, html, dcc,Input,Output
import pandas as pd
import plotly.express as px
import copy

df = pd.read_csv('https://raw.githubusercontent.com/plotly/datasets/master/gapminder_unfiltered.csv')

app = Dash(__name__)

tab1 = html.Div([
    html.H2("Select Continent(s)"),
    dcc.Dropdown(
        id="continent_dropdown",
        options=sorted(df["continent"].unique()),
        multi=True,
        searchable=True
    ),
    html.Br(),
    html.Div("Geospatial Data (by Continent)"),
    html.Br(),
    dcc.Loading(dcc.Graph(id="graph1"), type="cube")
])

tab2 = html.Div([
    html.H2("Select Country(ies)"),
    dcc.Dropdown(
        id="country_dropdown",
        options=sorted(df["country"].unique()),
        multi=True,
        searchable=True
    ),
    html.H4("Filter by Continent"),
    dcc.Dropdown(
        id="continent_filter_dropdown",
        options=sorted(df["continent"].unique()),
        multi=True,
        searchable=True
    ),
    html.Br(),
    html.Div("Geospatial Data (by Country)"),
    html.Br(),
    dcc.Loading(dcc.Graph(id="graph2"), type="cube")
])


def choropleth_map(data):
    fig = px.choropleth(data) 
    return fig


app.layout = html.Div([
    dcc.Tabs(
        id="tabs",
        value="continent",
        children=[
            dcc.Tab(
                label="Continent",
                value="continent",
                children=tab1
            ),
            dcc.Tab(
                label="Country",
                value="country",
                children=tab2
            )
            ]
    )
    ])


@app.callback(
    Output("graph1", "figure"),
    Input("continent_dropdown", "value")
)
def display_continent(continent_value):
    dff = copy.deepcopy(df)

    if continent_value:
        dff = df[df["continent"].isin(continent_value)]

    return choropleth_map(dff)


@app.callback(
    Output("country_dropdown", "options"),
    Input("continent_filter_dropdown", "value")
)
def set_country_options(continent_value):
    dff = copy.deepcopy(df)

    if continent_value is not None:
        #dff = dff.query("continent = @continent_value")
        dff[dff.continent.isin(continent_value)]
    return sorted(dff["country"].unique())


@app.callback(
    Output("graph2", "figure"),
    Input("country_dropdown", "value")
)
def display_country(country_value):
    dff = copy.deepcopy(df)

    if country_value:
        dff = df[df["country"].isin(country_value)]

    return choropleth_map(dff)


if __name__ == "__main__":
    app.run(debug=True)

PS: sorry I needed to change your column names according to the data that I used.

Cheers!

1 Like

I have edited my code following your suggestion and it works. Thank you so much for your help.

I got another small issue that I would like to fix. Since “multi” options is available, I am able to select multiple continents as criteria to filter the country dropdown list. The choropleth map in “graph2” is updated accordingly after selecting the country. After that, when I remove one of the continents in filter dropdown, the selected countries of the removed continent are visibly removed from the country dropdown, but the values are still retained.

I have double checked by reselect the removed continent into the filter section, and the previously removed countries will immediately reappear even without selecting them. How can I remove the values of the countries when I remove the continent in the filter section?

Current coding

  1. I filter the country dropdown by “Asia”, “Europe” and “North America”,
  2. I select “Japan”, “Germany”, “UK” and “US”,
  3. I remove “Europe” from the filter section, “Germany” and “UK” is removed from the country dropdown,
  4. I reselect “Europe”, “Germany” and “UK” automatically reselected in the country dropdown as the values retained in the background.

I want to edit my code so that “Germany” and “UK” will not be added immediately into the country dropdown, when I reselect the “Europe”. Any suggestion?

Hey @zeroeric2020,

Glad that you managed to get part of your code working. I understand your problem clearly, I have tried couple of things but couldn’t find a fix to your issue. Maybe @farispriadi has an idea about it…

Cheers!

Hey @zeroeric2020 could you post your current code here?

Please provide the data too (or equivalent data).

Hi @zeroeric2020 ,

It would be amazing if you can share your updated code with similar data.

And for now I will use code that has provided by @Berbere to try help solve your persistent country values when reselect previous continents.

I try to add a new callback function after set_country_options to update_country_value after reselect continent.

The main idea is to get intersection between new country list from new continent list and old country list.

After that it will be updated back to value of country_dropdown value.

This method will be create circular callback, so it will need allow_duplicate=True at output and prevent_initial_call=True to solve it.

import numpy as np

...


@app.callback(
    Output("country_dropdown", "options"),
    [Input("continent_filter_dropdown", "value")]
)
def set_country_options(continent_value):
    dff = copy.deepcopy(df)

    if continent_value:
        dff = df[df.continent.isin(continent_value)]
    return sorted(dff["country"].unique())


@app.callback(
    Output("country_dropdown", "value",allow_duplicate=True),
    [Input("continent_filter_dropdown", "value"),
    Input("country_dropdown","value")],prevent_initial_call=True
)
def update_country_value(continent_value, old_country_values):
    print("continent_value, old_country_values",continent_value, old_country_values)
    if old_country_values is not None and continent_value is not None:
        dff = df[df.continent.isin(continent_value)]
        current_country = np.intersect1d(dff.country.values,old_country_values)
        return current_country
    elif not continent_value:
        return []

 ...

I use numpy intersect1d function to intersect to list, so you need to import numpy.

The result wil be:
circular_dropdown_plotly5

and the runnable code will be:

from dash import Dash, html, dcc,Input,Output
import pandas as pd
import numpy as np
import plotly.express as px
import copy

df = pd.read_csv('https://raw.githubusercontent.com/plotly/datasets/master/gapminder_unfiltered.csv')

app = Dash(__name__)

tab1 = html.Div([
    html.H2("Select Continent(s)"),
    dcc.Dropdown(
        id="continent_dropdown",
        options=sorted(df["continent"].unique()),
        multi=True,
        searchable=True
    ),
    html.Br(),
    html.Div("Geospatial Data (by Continent)"),
    html.Br(),
    dcc.Loading(dcc.Graph(id="graph1"), type="cube")
])

tab2 = html.Div([
    html.H2("Select Country(ies)"),
    dcc.Dropdown(
        id="country_dropdown",
        options=sorted(df["country"].unique()),
        multi=True,
        searchable=True
    ),
    html.H4("Filter by Continent"),
    dcc.Dropdown(
        id="continent_filter_dropdown",
        options=sorted(df["continent"].unique()),
        multi=True,
        searchable=True
    ),
    # dcc.Store(id='intermediate-value'),
    html.Br(),
    html.Div("Geospatial Data (by Country)"),
    html.Br(),
    dcc.Loading(dcc.Graph(id="graph2"), type="cube")
])


def choropleth_map(data):
    fig = px.choropleth(data) 
    return fig


app.layout = html.Div([
    dcc.Tabs(
        id="tabs",
        value="continent",
        children=[
            dcc.Tab(
                label="Continent",
                value="continent",
                children=tab1
            ),
            dcc.Tab(
                label="Country",
                value="country",
                children=tab2
            )
            ]
    )
    ])


@app.callback(
    Output("graph1", "figure"),
    [Input("continent_dropdown", "value")]
)
def display_continent(continent_value):
    dff = copy.deepcopy(df)

    if continent_value:
        dff = df[df["continent"].isin(continent_value)]

    return choropleth_map(dff)


@app.callback(
    Output("country_dropdown", "options"),
    [Input("continent_filter_dropdown", "value")]
)
def set_country_options(continent_value):
    dff = copy.deepcopy(df)

    if continent_value:
        dff = df[df.continent.isin(continent_value)]
    return sorted(dff["country"].unique())


@app.callback(
    Output("country_dropdown", "value",allow_duplicate=True),
    [Input("continent_filter_dropdown", "value"),
    Input("country_dropdown","value")],prevent_initial_call=True
)
def update_country_value(continent_value, old_country_values):
    print("continent_value, old_country_values",continent_value, old_country_values)
    if old_country_values is not None and continent_value is not None:
        dff = df[df.continent.isin(continent_value)]
        current_country = np.intersect1d(dff.country.values,old_country_values)
        return current_country
    elif not continent_value:
        return []


@app.callback(
    Output("graph2", "figure"),
    Input("country_dropdown", "value")
)
def display_country(country_value):
    dff = copy.deepcopy(df)

    if country_value:
        dff = df[df["country"].isin(country_value)]

    return choropleth_map(dff)


if __name__ == "__main__":
    app.run(debug=True)

Hope this help. :slightly_smiling_face:

2 Likes