Updating Table based on selected row of another table

Let’s try this again and see if this post will go through or not.

As the title says I am trying to update the second table based on the selection on the first table. I have gotten it working for the first selection, but for any selections after that, the displayed data will not change. I am not sure what I am doing wrong. Any help that you guys can provide would be greatly appreciated. I have attached the code below in the hopes that one of you can see what I have done wrong.



######################### CODE #########################

import dash
import dash_core_components as dcc
import dash_html_components as html
import dash_bootstrap_components as dbc
from dash.dependencies import Input, Output
import pandas as pd
from gspread_pandas import Spread
import dash_table


from ..app import app


app.css.append_css({'external_url': 'https://codepen.io/amyoshino/pen/jzXypZ.css'})  # noqa: E501


data = [
    ['Not Fulfilled','2019-02-12 6:23:00','6425386','1589625','jim.john@aol.com','Bank of America','Jim','John',"Jim's Place",'Collector'],
    ['Not Fulfilled','2019-02-12 6:45:00','6798541','1578542','ssmith@yahoo.com','US Bank','Steve','Smith',"Smith's Store",'Vision'],
    ['Not Fulfilled','2019-02-12 8:16:00','6673825','1384408','thelastresort@msn.com','Chase','Sarah','Sica','The Last Resort','Firewall'],
    ['Not Fulfilled','2019-02-12 9:01:00','4435827','1756843','aaa@gmail.com','Wells Fargo','Eric','Williams','AAA','Firewall']
]
fulfillment = pd.DataFrame(data,columns=['Status', 'Created At', 'Internal ID', 'Invoice #', 'Customer Email', 'Processing Bank', 'First Name', 'Last Name', 'Company Name', 'Fulfillment Type'])

layout = html.Div([
        html.Div([
            dash_table.DataTable(
                id='datatable-interactivity',
                columns=[
                    {"name": i, "id": i} for i in fulfillment.columns
                ],
                data=fulfillment.to_dict("rows"),
                editable=False,
                sorting=True,
                sorting_type="single",
                row_selectable="single",
                selected_rows=[],
                style_table={'margin-top': 10, 'overflowX': 'scroll'},
                style_cell={'text-align': 'center'}
            ),
        ], className='ten columns offset-by-one'),
        
        html.Div(id='display_selected_row', className='eight columns offset-by-two')
    ])


@app.callback(
    Output('display_selected_row', "children"),
    [Input('datatable-interactivity', "derived_virtual_data"),
     Input('datatable-interactivity', "derived_virtual_selected_rows")])
def update_graph(rows, derived_virtual_selected_rows):
    if derived_virtual_selected_rows is None:
        derived_virtual_selected_rows = []

    if rows is None:
        dff = fulfillment
    else:
        dff = pd.DataFrame(rows)


    return html.Div([
                print(derived_virtual_selected_rows[0]),
                dash_table.DataTable(
                    id='display_selected_row',
                    columns=[
                        {"name": i, "id": i} for i in dff.columns
                    ],
                    data=dff.iloc[[derived_virtual_selected_rows[0]]].to_dict("rows"),
                    editable=False,
                    style_table={'margin-top': 10, 'overflowX': 'scroll'},
                    style_cell={'text-align': 'center'}
                ),
            ])

First a couple if nitpicks:

  1. Please provide code so that it’s immediately executable by someone else to test
  2. Your datatable isn’t virtualized, so I think it’s better to use the properties data and selected_rows directly rather than derived_virtual_data and derived_virtual_selected_rows
  3. I think it makes more sense to pass data in as a state not an input.

I’ve rewrote your code so I can execute it:

import dash
import dash_html_components as html
from dash.dependencies import Input, Output, State
import pandas as pd
import dash_table


app = dash.Dash(__name__)


app.css.append_css({'external_url': 'https://codepen.io/amyoshino/pen/jzXypZ.css'})  # noqa: E501


data = [
    ['Not Fulfilled', '2019-02-12 6:23:00', '6425386', '1589625', 'jim.john@aol.com', 'Bank of America', 'Jim', 'John',"Jim's Place", 'Collector'],
    ['Not Fulfilled', '2019-02-12 6:45:00', '6798541', '1578542', 'ssmith@yahoo.com', 'US Bank', 'Steve', 'Smith',"Smith's Store", 'Vision'],
    ['Not Fulfilled', '2019-02-12 8:16:00', '6673825', '1384408', 'thelastresort@msn.com', 'Chase', 'Sarah', 'Sica', 'The Last Resort', 'Firewall'],
    ['Not Fulfilled', '2019-02-12 9:01:00', '4435827', '1756843', 'aaa@gmail.com', 'Wells Fargo', 'Eric', 'Williams', 'AAA', 'Firewall']
]
fulfillment = pd.DataFrame(data, columns=['Status', 'Created At', 'Internal ID', 'Invoice #', 'Customer Email', 'Processing Bank', 'First Name', 'Last Name', 'Company Name', 'Fulfillment Type'])

app.layout = html.Div([
    html.Div([
        dash_table.DataTable(
            id='datatable-interactivity',
            columns=[
                {"name": i, "id": i} for i in fulfillment.columns
            ],
            data=fulfillment.to_dict("rows"),
            editable=False,
            sorting=True,
            sorting_type="single",
            row_selectable="single",
            selected_rows=[],
            style_table={'margin-top': 10, 'overflowX': 'scroll'},
            style_cell={'text-align': 'center'}
        ),
    ], className='ten columns offset-by-one'),

    html.Div(id='display_selected_row', className='eight columns offset-by-two')
])


@app.callback(
    output=Output('display_selected_row', "children"),
    inputs=[Input('datatable-interactivity', "selected_rows")],
    state=[State('datatable-interactivity', "data")])
def update_graph(selected_rows, rows):
    if selected_rows is None:
        selected_rows = []

    if rows is None:
        dff = fulfillment
    else:
        dff = pd.DataFrame(rows)

    dff = dff.iloc[selected_rows]

    return html.Div([
        dash_table.DataTable(
            id=f'display_selected_row',
            columns=[
                {"name": i, "id": i} for i in dff.columns
            ],
            data=dff.to_dict("rows")
        ),
    ])


if __name__ == '__main__':
    app.run_server()

With this cleaned up problem we see the issue is even more severe, it will not update at all.

But there’s a clue here, look at the id you’re inserting in to and the id you are creating the DataTable with, they are both “display_selected_row”. If we update your DataTable id to “display_selected_row_data_table” it now works:

import dash
import dash_html_components as html
from dash.dependencies import Input, Output, State
import pandas as pd
import dash_table


app = dash.Dash(__name__)


app.css.append_css({'external_url': 'https://codepen.io/amyoshino/pen/jzXypZ.css'})  # noqa: E501


data = [
    ['Not Fulfilled', '2019-02-12 6:23:00', '6425386', '1589625', 'jim.john@aol.com', 'Bank of America', 'Jim', 'John',"Jim's Place", 'Collector'],
    ['Not Fulfilled', '2019-02-12 6:45:00', '6798541', '1578542', 'ssmith@yahoo.com', 'US Bank', 'Steve', 'Smith',"Smith's Store", 'Vision'],
    ['Not Fulfilled', '2019-02-12 8:16:00', '6673825', '1384408', 'thelastresort@msn.com', 'Chase', 'Sarah', 'Sica', 'The Last Resort', 'Firewall'],
    ['Not Fulfilled', '2019-02-12 9:01:00', '4435827', '1756843', 'aaa@gmail.com', 'Wells Fargo', 'Eric', 'Williams', 'AAA', 'Firewall']
]
fulfillment = pd.DataFrame(data, columns=['Status', 'Created At', 'Internal ID', 'Invoice #', 'Customer Email', 'Processing Bank', 'First Name', 'Last Name', 'Company Name', 'Fulfillment Type'])

app.layout = html.Div([
    html.Div([
        dash_table.DataTable(
            id='datatable-interactivity',
            columns=[
                {"name": i, "id": i} for i in fulfillment.columns
            ],
            data=fulfillment.to_dict("rows"),
            editable=False,
            sorting=True,
            sorting_type="single",
            row_selectable="single",
            selected_rows=[],
            style_table={'margin-top': 10, 'overflowX': 'scroll'},
            style_cell={'text-align': 'center'}
        ),
    ], className='ten columns offset-by-one'),

    html.Div(id='display_selected_row', className='eight columns offset-by-two')
])


@app.callback(
    output=Output('display_selected_row', "children"),
    inputs=[Input('datatable-interactivity', "selected_rows")],
    state=[State('datatable-interactivity', "data")])
def update_graph(selected_rows, rows):
    if selected_rows is None:
        selected_rows = []

    if rows is None:
        dff = fulfillment
    else:
        dff = pd.DataFrame(rows)

    dff = dff.iloc[selected_rows]

    return html.Div([
        dash_table.DataTable(
            id='display_selected_row_data_table',
            columns=[
                {"name": i, "id": i} for i in dff.columns
            ],
            data=dff.to_dict("rows")
        ),
    ])


if __name__ == '__main__':
    app.run_server()

Thank you very much Damian for your help in fixing my issue!

  1. I am sorry I thought that I had made enough changes to it so that it could just be copied and pasted and it would work.

  2. The reason why I had used derived_virtual_data and derived_virtual_selected_rows is because I built this off of the example ‘Interactive Tables’ in the Dash Core Components and those are what are used there.

Makes sense!

This is an easy trap to fall in to, as Dash gives no error that 2 different elements have the same id (to be fair in this case it would be quite hard to spot as the 2nd element is inserted dynamically).

I have built myself a small framework that namespaces and dynamically assigns elements at layout time, so an element in 1 page gets called “page1.element1”, “page1.element2” etc… But it wasn’t a small task to get this done.