Awesome!
I actually had been working on this some and after doing a lot of searching, I came across the pattern matching that you had utilized and had come up with my own solution (see below)
from dash import dcc, html, Dash, Output, Input, State, callback_context, ctx, ALL
from dash.exceptions import PreventUpdate
from dash import dash_table as dt
import pandas as pd
customer_data = pd.DataFrame([{'name': 'Anna', 'contact_method': 'email', 'age': 31},
{'name': 'Bob', 'contact_method': 'email', 'age': 41},
{'name': 'Cody', 'contact_method': 'phone', 'age': 51},
{'name': 'David', 'contact_method': 'email', 'age': 27},
{'name': 'Evan', 'contact_method': 'phone', 'age': 17}])
transaction_data = pd.DataFrame([{'name': 'David', 'item': 'Apple', 'qty': 2, 'cost': 4},
{'name': 'David', 'item': 'Banana', 'qty': 5, 'cost': 2},
{'name': 'David', 'item': 'Orange', 'qty': 3, 'cost': 2},
{'name': 'Bob', 'item': 'Banana', 'qty': 1, 'cost': 0.4},
{'name': 'Bob', 'item': 'Apple', 'qty': 2, 'cost': 4},
{'name': 'Evan', 'item': 'Orange', 'qty': 9, 'cost': 6},
{'name': 'Anna', 'item': 'Banana', 'qty': 3, 'cost': 1.2},
{'name': 'Cody', 'item': 'Apple', 'qty': 3, 'cost': 6}])
app = Dash(__name__)
data_style_conditional = [
{
'if': {'state': 'active'},
'backgroundColor': 'rgba(150, 180, 225, 0.2)',
'border': '1px solid blue'
},
{
'if': {'state': 'selected'},
'backgroundColor': 'rgba(0, 116, 217, 0.03)',
'border': '1px solid blue'
}]
app.layout = html.Div([
dcc.Dropdown(value='All',
options= ['All'] + list(transaction_data['item'].unique()),
id='item_dropdown'),
html.Button('Clear',
id='clear'),
dt.DataTable(data=customer_data.to_dict('records'),
columns=[{'name': ' '.join([x.title() for x in column.split('_')]),
'id': column} for column in customer_data.columns],
style_data_conditional=data_style_conditional,
id={'type': 'table', 'index':0}),
dt.DataTable(data=transaction_data.to_dict('records'),
columns=[{'name': ' '.join([x.title() for x in column.split('_')]),
'id': column} for column in transaction_data.columns],
style_data_conditional=data_style_conditional,
id={'type': 'table', 'index':1}),
dcc.Store(data='',
id='last_action')
])
@app.callback(
Output({'type':'table', 'index':ALL}, 'selected_cells'),
Output({'type':'table', 'index':ALL}, 'active_cell'),
Output({'type':'table', 'index':ALL}, 'style_data_conditional'),
Output('last_action', 'data'),
Input({'type':'table', 'index':ALL}, 'selected_cells'),
State({'type':'table', 'index':ALL}, 'id'),
Input('clear', 'n_clicks'),
Input('item_dropdown', 'value'),
prevent_initial_call=True
)
def unselect_cells(cells, tables, n_clicks, item):
e = callback_context.triggered[0]['prop_id']
if 'selected_cells' not in e:
return [[]] * len(tables), [None] * len(tables), [data_style_conditional] * len(tables), e
table_ids = [t['index'] for t in tables]
trigger = ctx.triggered_id.index
new_selection = [[] if t!=trigger else c for c, t in zip(cells, table_ids)]
new_active = [None if s==[] else s[0] for s in new_selection]
new_style = [data_style_conditional if active is None else data_style_conditional+[{'if': {'row_index': active['row']},'backgroundColor': 'rgba(150, 180, 225, 0.2)','border': '1px solid blue'}] for active in new_active]
return new_selection, new_active, new_style, e
@app.callback(Output({'type': 'table', 'index': 0}, 'data'),
Input('item_dropdown', 'value'),
Input({'type': 'table', 'index': 1}, 'active_cell'),
Input({'type': 'table', 'index': 1}, 'derived_virtual_data'),
State('last_action', 'data'))
def filter_customer_tbl(item, cell, data, last_action):
item_filter = customer_data['name'].isin(transaction_data[transaction_data['item'] == item]['name'].unique()) if item != 'All' else [True] * len(customer_data.index)
if str(last_action) == '{"index":0,"type":"table"}.selected_cells':
raise PreventUpdate
if cell:
return customer_data[(customer_data['name'] == data[cell['row']].get('name')) & item_filter].to_dict('records')
return customer_data[item_filter].to_dict('records')
@app.callback(Output({'type': 'table', 'index': 1}, 'data'),
Input('item_dropdown', 'value'),
Input({'type': 'table', 'index': 0}, 'active_cell'),
Input({'type': 'table', 'index': 0}, 'derived_virtual_data'),
State('last_action', 'data'))
def filter_transaction_tbl(item, cell, data, last_action):
item_filter = transaction_data['item'] == item if item != 'All' else [True] * len(transaction_data.index)
if str(last_action) == '{"index":1,"type":"table"}.selected_cells':
raise PreventUpdate
if cell:
return transaction_data[(transaction_data['name'] == data[cell['row']].get('name')) & item_filter].to_dict('records')
return transaction_data[item_filter].to_dict('records')
if __name__ == "__main__":
app.run_server(debug=True)
I meant to post this earlier, but I started working on a couple other things. It is similar to what your code does, but there are some differences. For one, I don’t filter the customer table based on the item type that the transaction had. Instead I filter the customer table to the customer related to the transaction. I also wanted to have the “clear” button more than anything to undo the table filtering (since the thought was that if I filter one table and then filter the other you would be stuck with those tables), so my clear button only clears the table interactions.
The major difference is that when you click on table to filter and then go to the other, the filtered table that you are now using as a filter for the other does not change.
Regardless of that, your solution is great! I really appreciate you taking the time to solve this and there are some things that I will use in my code (such as the highlighting of cells). I’ll just leave this code here as an alternative
Thank you!