Following the discovery of what (to me) looks like a bug (discrepancy between the actual selection of rows, and the value of derived_viewport_selected_row_ids
- which looks like something I also reported a while back, I found one simple technique, which basically compares the selected_row_ids
, derived_viewport_row_ids
and (incorrect) derived_viewport_selected_row_ids
to find the difference, and thereby determining the last ticked or unticked row in the table (without the need for a dcc.Store
.
Example below.
I’m not sure however whether I can rely on this, given that this looks like a bug, and may get fixed one day.
from dash import Dash, dash_table, dcc, html
from dash.dependencies import Input, Output, State
import pandas as pd
df = pd.read_csv('https://raw.githubusercontent.com/plotly/datasets/master/gapminder2007.csv')
df['id'] = df['country']
app = Dash(__name__)
app.layout = html.Div([
html.P(id='placeholder'),
dash_table.DataTable(
id='datatable-interactivity',
columns=[
{"name": i, "id": i, "deletable": True, "selectable": True} for i in df.columns
],
data=df.to_dict('records'),
editable=True,
filter_action="native",
sort_action="native",
sort_mode="multi",
column_selectable="single",
row_selectable="multi",
row_deletable=True,
selected_columns=[],
selected_rows=[],
page_action="native",
page_current= 0,
page_size= 10,
),
html.Div(id='datatable-interactivity-container')
])
@app.callback(
Output('datatable-interactivity', 'style_data_conditional'),
Input('datatable-interactivity', 'selected_columns')
)
def update_styles(selected_columns):
return [{
'if': { 'column_id': i },
'background_color': '#D2F3FF'
} for i in selected_columns]
@app.callback(
Output('datatable-interactivity-container', "children"),
Input('datatable-interactivity', "derived_virtual_data"),
Input('datatable-interactivity', "derived_virtual_selected_rows"))
def update_graphs(rows, derived_virtual_selected_rows):
# When the table is first rendered, `derived_virtual_data` and
# `derived_virtual_selected_rows` will be `None`. This is due to an
# idiosyncrasy in Dash (unsupplied properties are always None and Dash
# calls the dependent callbacks when the component is first rendered).
# So, if `rows` is `None`, then the component was just rendered
# and its value will be the same as the component's dataframe.
# Instead of setting `None` in here, you could also set
# `derived_virtual_data=df.to_rows('dict')` when you initialize
# the component.
if derived_virtual_selected_rows is None:
derived_virtual_selected_rows = []
dff = df if rows is None else pd.DataFrame(rows)
colors = ['#7FDBFF' if i in derived_virtual_selected_rows else '#0074D9'
for i in range(len(dff))]
return [
dcc.Graph(
id=column,
figure={
"data": [
{
"x": dff["country"],
"y": dff[column],
"type": "bar",
"marker": {"color": colors},
}
],
"layout": {
"xaxis": {"automargin": True},
"yaxis": {
"automargin": True,
"title": {"text": column}
},
"height": 250,
"margin": {"t": 10, "l": 10, "r": 10},
},
},
)
# check if column exists - user may have deleted it
# If `column.deletable=False`, then you don't
# need to do this check.
for column in ["pop", "lifeExp", "gdpPercap"] if column in dff
]
@app.callback(
[Output("placeholder", "children")],
[Input("datatable-interactivity", "selected_row_ids")],
[
State("datatable-interactivity", "derived_viewport_row_ids"),
State("datatable-interactivity", "derived_viewport_selected_row_ids"),
State("datatable-interactivity", "derived_virtual_row_ids"),
State("datatable-interactivity", "derived_virtual_selected_row_ids"),
State("datatable-interactivity", "data")
])
def see_row_ids(selected_row_ids, derived_viewport_row_ids, derived_viewport_selected_row_ids, derived_virtual_row_ids,
derived_virtual_selected_row_ids, data):
out = []
if selected_row_ids:
out.append(html.P("selected_row_ids ({}): {}\n".format(len(selected_row_ids), selected_row_ids)))
if derived_viewport_row_ids:
out.append(html.P("derived_viewport_row_ids ({}): {}".format(len(derived_viewport_row_ids), derived_viewport_row_ids)))
if not derived_viewport_selected_row_ids:
derived_viewport_selected_row_ids = []
out.append(html.P("derived_viewport_selected_row_ids ({}): {}".format(len(derived_viewport_selected_row_ids), derived_viewport_selected_row_ids)))
if derived_virtual_row_ids:
out.append(html.P("derived_virtual_row_ids ({}): {}".format(len(derived_virtual_row_ids), derived_virtual_row_ids)))
if not derived_virtual_selected_row_ids:
derived_virtual_selected_row_ids = []
out.append(html.P("derived_virtual_selected_row_ids ({}): {}".format(len(derived_virtual_selected_row_ids), derived_virtual_selected_row_ids)))
all_selected = selected_row_ids
if not all_selected:
all_selected = []
if not derived_virtual_row_ids:
derived_virtual_row_ids = []
selected_in_view = [x for x in derived_virtual_row_ids if x in all_selected]
previously_selected_in_view = derived_viewport_selected_row_ids
last_selection = [x for x in selected_in_view if x not in previously_selected_in_view]
last_deselection = [x for x in previously_selected_in_view if x not in selected_in_view]
if last_selection:
out.append(html.P(f"last selected {last_selection}"))
if last_deselection:
out.append(html.P(f"last deselected {last_deselection}"))
# difference = [x for x in ]
return [out]
if __name__ == '__main__':
app.run_server(debug=True)