Hello!
Thank you for the code.
The problem is : you need to update selected_rows
when updating the data table, because the ids you had before filtering will not correspond to the ids you have after filtering.
With your example
id category value
0 1 A 10
1 2 B 20
2 3 A 15
3 4 B 25
4 5 A 30
5 6 B 45
6 7 A 50
7 8 B 35
8 9 A 40
9 10 B 55
If you select on rows with id=[2, 4, 6]
then you’ll have selected_rows=[1, 2, 3]
Then, you use the dropdown filter to select only category B.
The table gets updated to :
id category value
1 2 B 20
3 4 B 25
5 6 B 45
7 8 B 35
9 10 B 55
So in this case, you need to update selected_rows to [0, 1, 2]
, because ids 1, 2 and 3 are respectively in position 0, 1, and 2 of the dataframe. Having selected_rows=[1, 2, 3]
won’t work anymore.
But you’ll soon struggle to keep track of the correct ids / indexes to use. To avoid this, you can set a specific index to your dataframe and use selected_row_ids
in place of selected_rows
.
Learn more about this here: Sorting, Filtering, Selecting, and Paging Natively | Dash for Python Documentation | Plotly (row IDs section).
Here is a code that worked for this example.
import dash
from dash import dcc, html, Input, Output, State, dash_table, callback_context, no_update
import pandas as pd
print("Starting...")
df = pd.DataFrame({
"id": range(1, 11),
"category": ["A", "B"] * 5,
"value": [10, 20, 15, 25, 30, 45, 50, 35, 40, 55],
})
print(df)
df.set_index("id", inplace=True, drop=False)
app = dash.Dash(__name__)
app.layout = html.Div([
html.Div([
dcc.Dropdown(
id="category-filter",
options=[{"label": cat, "value": cat} for cat in df["category"].unique()],
multi=True,
placeholder="Filter by category",
),
]),
dash_table.DataTable(
id="data-table",
columns=[{"name": col, "id": col} for col in df.columns],
data=df.to_dict("records"),
row_selectable="multi",
selected_rows=[],
filter_action="native",
page_size=25,
),
])
n = 0
@app.callback(
Output("data-table", "data"),
Output("data-table", "selected_rows"),
Input("category-filter", "value"),
Input("data-table", "selected_row_ids"),
State("data-table", "derived_virtual_data"),
)
def update_table(filter_values, selected_row_ids, data):
"""
Update table based on filter component, datatable native filters, maintain row selection
Filters:
- limit rows that are shown in table (ideally previoulsy selected rows remain and appear at the top)
datatable filters:
- limit rows that are shown in table (ideally any previoulsy selected rows remain and appear at the top)
row selection:
- will change a chart component
"""
global n
ctx = callback_context
triggered_id = ctx.triggered_id # ctx.triggered[0]["prop_id"].split(".")[0]
prop_id = ctx.triggered[0]['prop_id']
print(f"\n{n}_Callback trigger with ctx prop: {prop_id}")
if selected_row_ids:
print(f"{n}_ Number of rows selected: {len(selected_row_ids)}")
selected = df.loc[selected_row_ids] # Changed iloc to loc here
print(f"{n}_ Selected: \n{selected}")
if data:
print(f"{n}_ Number of rows shown: {len(data)}")
dff = df
n += 1
if triggered_id == "category-filter":
print(f"{n}_FILTERS")
# Apply filtering
if filter_values:
dff = df[df["category"].isin(filter_values)]
print(f"{n}_dff updated, new len: {len(dff)}")
else:
dff = df
elif prop_id == "data-table.selected_rows":
print(f"{n}_ROW SELECTION")
elif prop_id == "data-table.derived_virtual_data":
print(f"{n}_DATATABLE FILTER {prop_id}")
else:
print(f"{n}_Other event... {prop_id}")
print("dff")
print(dff)
print("selected_row_ids", selected_row_ids)
selected_row_ids = selected_row_ids or []
selected_rows = [i for i, index in enumerate(dff.index) if index in selected_row_ids]
print("selected_rows will be ", selected_row_ids)
return dff.to_dict("records"), selected_rows
if __name__ == '__main__':
app.run_server(debug=True)
I did not test it further (interaction with the native filter component, etc.)
But at least you have new insights (I hope).
Hope it helps 