After user unselects in checklist, value is selected back automatically

Dashboard idea (dash-2.7.1):
I have one variable for slider (‘Average MW Per Plant’), and another variable (‘State’) for checklist. A datatable will be updated upon user interaction.
Three (logical) steps of user interaction:

  1. Users set the slider variable first (now working fine);
  2. Checklist options will get updated based on the filtered dataframe generated by Step 1, and all checklist values will be selected by default (now working fine);
  3. Users select/unselect the values among the updated checklist options generated by Step 2 (now problematic);

Symptom in Step 3:
When user unselects a checklist value, it is selected back automatically (but somehow the datatable filtering is still working from what it is displayed).

More details:
In my first callback, only the checklist options are updated, not the checklist values. In my second callback, I explicitly tell Dash that only after the slider is moved (not when checklist is selected/unselected), all values will be selected. Anyone has an idea what the problems could be?

Minimal reproducible example:

from dash import Dash, Input, Output, dash_table
from dash import dcc, html
from dash.dash import no_update
import pandas as pd
import dash_bootstrap_components as dbc

app = Dash(__name__, external_stylesheets=[dbc.themes.LITERA])
server = app.server

df = pd.read_csv('https://git.io/Juf1t')

# initialize a filtered dataframe when dashboard is first loaded
# filtering is based on 'Average MW Per Plant' (a variable used for slider defined below)
df_filtered = df.loc[df['Average MW Per Plant'].between(10.0, 23.0, inclusive='both'), df.columns]

# Create state list for checklists based on the complete data
# This list is used every time slider is moved so that all values of checklist are selected
complete_states = sorted(df['State'].unique())

# Create state list for checklists based on the initial slider-filtered dataframe
chlst = sorted(df_filtered['State'].unique())

app.layout = dbc.Container([
    html.Hr(),
    dbc.Row([
        dbc.Label("Set a range of Average MW Per Plant:"),
    dcc.RangeSlider( # slider
                id='amw-slider',
                min=4.0,
                max=23.0,
                value=[10.0, 23.0]
    ),
    html.Hr(),
    dcc.Checklist( # checklist of states
                id='chlst',
                options=[{'label':x, 'value':x} for x in chlst],
                value=chlst,
                labelClassName="mr-3"
    ),
    html.Hr(),
    dash_table.DataTable(df_filtered.to_dict('records'),
                columns=([{"name": i, "id": i} for i in df_filtered.columns]),
                id='tbl')
    ])
])

# first callback
@app.callback(Output('tbl', 'data'),
              Output('chlst', 'options'),
              Input('amw-slider', 'value'),
              Input('chlst', 'value'))
def update_table(amw, chlst):
    # filter dataframe based on user interaction with slider
    filtered_df = df.loc[df['Average MW Per Plant'].between(amw[0], amw[1], inclusive='both'), df.columns]

    # get the updated state list based on user interaction with slider
    filtered_chlst = sorted(filtered_df['State'].unique().tolist())
    
    # further filter dataframe based on user interaction with checklist
    for idx, x in filtered_df.iterrows():
        if x['State'] not in set(chlst):
            filtered_df.drop(idx, inplace=True)

    return filtered_df.to_dict('records'), filtered_chlst

# second callback that resets checklist to "select all" once slider is moved
@app.callback(
    Output('chlst', 'value'),
    Input('amw-slider', 'value')
)
def check_all(amw):
    if not amw:
        return no_update
    else:
        return complete_states

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

Hi @wtc, what do you want to happen if the checklist item has been unchecked?

EDIT:
I assume you want to filter the table and show only the items within the slider range AND if they are checked. I tried to change your initial code as little as possible. There are still two callbacks as you describe in your post

I just moved the updating of the table into the second callback. Here the full code:

from dash import Dash, Input, Output, dash_table, State
from dash import dcc, html
import pandas as pd
import dash_bootstrap_components as dbc

app = Dash(__name__, external_stylesheets=[dbc.themes.LITERA])
server = app.server

df = pd.read_csv('https://git.io/Juf1t')

# initialize a filtered dataframe when dashboard is first loaded
# filtering is based on 'Average MW Per Plant' (a variable used for slider defined below)
df_filtered = df.loc[df['Average MW Per Plant'].between(10.0, 23.0, inclusive='both'), df.columns]

# Create state list for checklists based on the complete data
# This list is used every time slider is moved so that all values of checklist are selected
complete_states = sorted(df['State'].unique())

# Create state list for checklists based on the initial slider-filtered dataframe
chlst = sorted(df_filtered['State'].unique())

app.layout = dbc.Container([
    html.Hr(),
    dbc.Row([
        dbc.Label("Set a range of Average MW Per Plant:"),
        dcc.RangeSlider(  # slider
            id='amw-slider',
            min=4.0,
            max=23.0,
            value=[10.0, 23.0]
        ),
        html.Hr(),
        dcc.Checklist(  # checklist of states
            id='chlst',
            options=[{'label': x, 'value': x} for x in chlst],
            value=chlst,
            labelClassName="mr-3"
        ),
        html.Hr(),
        dash_table.DataTable(df_filtered.to_dict('records'),
                             columns=([{"name": i, "id": i} for i in df_filtered.columns]),
                             id='tbl'),
    ])
])


# first callback updates the visible checklist options
@app.callback(
    Output('chlst', 'options'),
    Output('chlst', 'value'),
    Input('amw-slider', 'value'),
)
def update_table(amw):
    # filter dataframe based on user interaction with slider
    filtered_df = df.loc[df['Average MW Per Plant'].between(amw[0], amw[1], inclusive='both'), df.columns]

    # get the updated state list based on user interaction with slider
    filtered_chlst = sorted(filtered_df['State'].unique().tolist())

    return filtered_chlst, filtered_chlst


# second callback updates the table based on the selected checklist items
@app.callback(
    Output('tbl', 'data'),
    Input('chlst', 'value'),
    State('amw-slider', 'value')
)
def update_table(chlst, amw):
    # filter dataframe based on user interaction with slider
    filtered_df = df.loc[df['Average MW Per Plant'].between(amw[0], amw[1], inclusive='both'), df.columns]

    # further filter dataframe based on user interaction with checklist
    for idx, x in filtered_df.iterrows():
        if x['State'] not in set(chlst):
            filtered_df.drop(idx, inplace=True)

    return filtered_df.to_dict('records')


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

table

Thank you! Your code works as expected.

1 Like