plotly / dash-table

OBSOLETE: now part of https://github.com/plotly/dash
https://dash.plotly.com
MIT License
421 stars 73 forks source link

Dash Table with row_selectable="multi" does not respond to data or columns callbacks #436

Open shavavo opened 5 years ago

shavavo commented 5 years ago

DataTable with row_selectable="multi" does not respond to any columns or data callbacks.

Debug shows Javascript error:

TypeError: Cannot read property '0' of undefined

    at e.getWeight (http://127.0.0.1:8050/_dash-component-suites/dash_table/bundle.js?v=3.7.0&m=1557928877:18:65588)

    at http://127.0.0.1:8050/_dash-component-suites/dash_table/bundle.js?v=3.7.0&m=1557928877:18:98263

    at http://127.0.0.1:8050/_dash-component-suites/dash_table/bundle.js?v=3.7.0&m=1557928877:18:275516

    at http://127.0.0.1:8050/_dash-component-suites/dash_table/bundle.js?v=3.7.0&m=1557928877:12:10031

    at Object.t [as a] (http://127.0.0.1:8050/_dash-component-suites/dash_table/bundle.js?v=3.7.0&m=1557928877:1:1711)

    at e.value (http://127.0.0.1:8050/_dash-component-suites/dash_table/bundle.js?v=3.7.0&m=1557928877:18:98224)

    at http://127.0.0.1:8050/_dash-component-suites/dash_table/bundle.js?v=3.7.0&m=1557928877:18:96967

    at e.memoizedCreateEdges (http://127.0.0.1:8050/_dash-component-suites/dash_table/bundle.js?v=3.7.0&m=1557928877:1:2020)

    at e.value (http://127.0.0.1:8050/_dash-component-suites/dash_table/bundle.js?v=3.7.0&m=1557928877:18:99012)

    at http://127.0.0.1:8050/_dash-component-suites/dash_table/bundle.js?v=3.7.0&m=1557928877:18:115236

Example:

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

df = pd.read_csv('https://raw.githubusercontent.com/plotly/datasets/master/solar.csv')

app = dash.Dash(__name__)

app.layout = html.Div(children=[
    dash_table.DataTable(
        id='table',
        row_selectable="multi"
    ),
    html.Div(id='dummy_input')
])

@app.callback(Output('table', 'columns'), [Input('dummy_input', 'children')])
def update_columns(dummy):
    return [{"name": i, "id": i} for i in df.columns]

@app.callback(Output('table', 'data'), [Input('dummy_input', 'children')])
def update_data(dummy):
    return df.to_dict('records')

if __name__ == '__main__':
    app.run_server(debug=True)
rp2532 commented 4 years ago

I run into the same issue, with a very similar stack trace, but only when I add the property filter_action = 'native' to my DataTable. Anyone have an idea what might be breaking? Below is the part of my code where I create the DataTable. The app, including the table, works fine if I remove the filter_action property.

dash_table.DataTable(
        id = 'files-table',
        columns = files_table_columns,
        data = [],
        row_selectable = 'multi',
        row_deletable = True,
        editable = False,
        dropdown = {},
        sort_action = 'native',
        filter_action = 'native',
        style_header = {
            'backgroundColor': 'rgb(230, 230, 230)',
            'fontWeight': 'bold'
        },
        style_table = {
            'maxHeight': '300px',
            'overflowY': 'scroll'
        },
        fixed_rows = {
            'headers': True,
            'data': 0
        }
    ),
rp2532 commented 4 years ago

Incidentally I just found that the filtering action works fine if I remove the lines row_selectable = 'multi', row_deletable = True, So it seems like something about the combination of selectable AND deletable rows AND the filter_action is resulting in the problem.

rp2532 commented 4 years ago

@Marc-Andre-Rivet seems to me that this could be related to #447

phildibert commented 4 years ago

@rp2532 I have created a table with editable=True and filter_action=native. When I try and add in row_deletable=True or row_selectable=True, the table does not render.

It seems that having filter_action=native in combination with (row_deletable=True OR row_selectable="multi") is not possible, like you are seeing.

wolfgangpfnuer commented 4 years ago

I'm also having a problem with the combination of row_selectable=multi and filter_action=native. getting the same error Cannot read property '0' of undefined. developer tools shows this in details:

(This error originated from the built-in JavaScript code that runs Dash apps. Click to see the full stack trace or open your browser's console.)
TypeError: Cannot read property '0' of undefined

    at e.getWeight (http://localhost:3000/_dash-component-suites/dash_table/async-table.v4_6_1m1582839172.js:7:117699)

    at http://localhost:3000/_dash-component-suites/dash_table/async-table.v4_6_1m1582839172.js:7:131047

    at http://localhost:3000/_dash-component-suites/dash_table/async-table.v4_6_1m1582839172.js:7:197843

    at http://localhost:3000/_dash-component-suites/dash_table/async-table.v4_6_1m1582839172.js:1:427

    at Object.t [as a] (http://localhost:3000/_dash-component-suites/dash_table/bundle.v4_6_1m1583158233.js:1:6127)

    at e.value (http://localhost:3000/_dash-component-suites/dash_table/async-table.v4_6_1m1582839172.js:7:131008)

    at http://localhost:3000/_dash-component-suites/dash_table/async-table.v4_6_1m1582839172.js:7:129754

    at e.memoizedCreateEdges (http://localhost:3000/_dash-component-suites/dash_table/bundle.v4_6_1m1583158233.js:1:10407)

    at e.value (http://localhost:3000/_dash-component-suites/dash_table/async-table.v4_6_1m1582839172.js:7:131814)

    at t.tableFn (http://localhost:3000/_dash-component-suites/dash_table/async-table.v4_6_1m1582839172.js:7:156594)

dash used is as follows on python 3.8.1:

dash==1.9.1
dash-core-components==1.8.1
dash-cytoscape==0.1.1
dash-html-components==1.0.2
dash-renderer==1.2.4
dash-table==4.6.1
montovaneli commented 4 years ago

@wolfgangpfnuer i went through the same problem and i realized that if i create my DataTable using an empty pandas DataFrame with one column, it works. Try it yourself, for example:

This one will throw the "Cannot read property '0' of undefined" error:

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

df = pd.read_csv('https://raw.githubusercontent.com/plotly/datasets/master/solar.csv')

app = dash.Dash(__name__)

app.layout = html.Div(children=[
    dash_table.DataTable(
        id='table',
        filter_action='native',
        row_selectable="single"
    ),
    html.Div(id='dummy_input')
])

@app.callback(Output('table', 'columns'), [Input('dummy_input', 'children')])
def update_columns(dummy):
    return [{"name": i, "id": i} for i in df.columns]

@app.callback(Output('table', 'data'), [Input('dummy_input', 'children')])
def update_data(dummy):
    return df.to_dict('records')

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

However, this one works:

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

df = pd.read_csv('https://raw.githubusercontent.com/plotly/datasets/master/solar.csv')

app = dash.Dash(__name__)

start_table_df = pd.DataFrame(columns=['Start Column'])  ####### inserted line

app.layout = html.Div(children=[
    dash_table.DataTable(
       data=start_table_df.to_dict('records'), ####### inserted line
       columns = [{'id': c, 'name': c} for c in start_table_df.columns], ####### inserted line
        id='table',
        filter_action='native',
        row_selectable='single'
    ),
    html.Div(id='dummy_input')
])

@app.callback(Output('table', 'columns'), [Input('dummy_input', 'children')])
def update_columns(dummy):
    return [{"name": i, "id": i} for i in df.columns]

@app.callback(Output('table', 'data'), [Input('dummy_input', 'children')])
def update_data(dummy):
    return df.to_dict('records')

if __name__ == '__main__':
    app.run_server(debug=True)
rp2532 commented 4 years ago

@montovaneli I cannot reproduce your workaround - if I have a data_table with BOTH filter_action and row_selectable enabled, I get the error upon startup that says TypeError: Cannot read property '0' of undefined However, if only one of filter_action or row_selectable is used (or neither of them), the error does not appear.

Code below is modified from keval's post on this page - https://community.plotly.com/t/datatable-fails-to-update-on-callback/32720/4


import dash
import dash_core_components as dcc 
import dash_html_components as html 
from dash.dependencies import Input, Output, State
from dash.exceptions import PreventUpdate
import dash_table
from plotly.subplots import make_subplots

import os
import numpy as np 
import pandas as pd
import io
import base64

# Empty DataFrame to start the DataTable (workaround for bug in DataTable - https://github.com/plotly/dash-table/issues/436#issuecomment-615924723)
start_table_df = pd.DataFrame(columns=['Start Column'])

# Create dash app
app = dash.Dash(__name__)

### LAYOUT ###
# App Layout
app.layout = html.Div([
    html.H3("Post Covid-19 Tool"),

    dcc.Upload(
        id='upload-data',
        children=html.Div([
            'Drag and Drop or ',
            html.A('Select Files')
        ]),
        style={
            'width': '100%',
            'height': '60px',
            'lineHeight': '60px',
            'borderWidth': '1px',
            'borderStyle': 'dashed',
            'borderRadius': '5px',
            'textAlign': 'center',
            'margin': '10px'
        },
        # Allow multiple files to be uploaded
        multiple=False
    ),

    html.Div([
        dash_table.DataTable(
            data=start_table_df.to_dict('records'), 
            columns = [{'id': c, 'name': c} for c in start_table_df.columns],
            id='emp-table',
            #page_size = 14,
            #sort_action='native',
            filter_action='native',
            row_selectable='single',
            #row_deletable=True,
            #editable=True
        ),
    ])
])

# file upload function
def parse_contents(contents, filename):

    content_type, content_string = contents.split(',')

    decoded = base64.b64decode(content_string)

    try:

        if 'csv' in filename:
            # Assume that the user uploaded a CSV file
            df = pd.read_csv(io.StringIO(decoded.decode('utf-8')))

        elif 'xls' in filename:
            # Assume that the user uploaded an excel file
            df = pd.read_excel(io.BytesIO(decoded))

    except Exception as e:
        print(e)
        return None

    return df

### CALLBACKS ###
# Callback: Return Datatable
@app.callback([Output('emp-table', 'data'),
               Output('emp-table', 'columns')],
              [Input('upload-data', 'contents')],
              [State('upload-data', 'filename')])
def render_table(contents, filename):

    if contents and filename:

        df_geo = parse_contents(contents, filename)

        columns = [{"name": i, "id": i} for i in df_geo.columns]

        data = df_geo.to_dict('records')

        # Debug
        print(df_geo.head())

        return data, columns

    else:

        return [], []

if __name__ == '__main__':
    app.run_server(debug=True, port=8000)
rp2532 commented 4 years ago

@Marc-Andre-Rivet @chriddyp Any insight into this issue, i.e. why filter_action does not work in combination with either row_selectable or editable or row_deletable? Any idea when it might be fixed?

Incidentally a related issue on the community forum is in keval's comment on this page - https://community.plotly.com/t/datatable-fails-to-update-on-callback/32720/5

montovaneli commented 4 years ago

@rp2532 that is because you are returning empty lists when you don't have uploaded data. Try this:

import dash
import dash_core_components as dcc 
import dash_html_components as html 
from dash.dependencies import Input, Output, State
from dash.exceptions import PreventUpdate
import dash_table
from plotly.subplots import make_subplots

import os
import numpy as np 
import pandas as pd
import io
import base64

# Empty DataFrame to start the DataTable (workaround for bug in DataTable - https://github.com/plotly/dash-table/issues/436#issuecomment-615924723)
start_table_df = pd.DataFrame(columns=[''])

# Create dash app
app = dash.Dash(__name__)

### LAYOUT ###
# App Layout
app.layout = html.Div([
    html.H3("Post Covid-19 Tool"),

    dcc.Upload(
        id='upload-data',
        children=html.Div([
            'Drag and Drop or ',
            html.A('Select Files')
        ]),
        style={
            'width': '100%',
            'height': '60px',
            'lineHeight': '60px',
            'borderWidth': '1px',
            'borderStyle': 'dashed',
            'borderRadius': '5px',
            'textAlign': 'center',
            'margin': '10px'
        },
        # Allow multiple files to be uploaded
        multiple=False
    ),

    html.Div([
        dash_table.DataTable(
            data=start_table_df.to_dict('records'), 
            columns = [{'id': c, 'name': c} for c in start_table_df.columns],
            id='emp-table',
            #page_size = 14,
            #sort_action='native',
            filter_action='native',
            row_selectable='single',
            #row_deletable=True,
            #editable=True
        ),
    ])
])

# file upload function
def parse_contents(contents, filename):

    content_type, content_string = contents.split(',')

    decoded = base64.b64decode(content_string)

    try:

        if 'csv' in filename:
            # Assume that the user uploaded a CSV file
            df = pd.read_csv(io.StringIO(decoded.decode('utf-8')))

        elif 'xls' in filename:
            # Assume that the user uploaded an excel file
            df = pd.read_excel(io.BytesIO(decoded))

    except Exception as e:
        print(e)
        return None

    return df

### CALLBACKS ###
# Callback: Return Datatable
@app.callback([Output('emp-table', 'data'),
               Output('emp-table', 'columns')],
              [Input('upload-data', 'contents')],
              [State('upload-data', 'filename')])
def render_table(contents, filename):

    if contents and filename:

        df_geo = parse_contents(contents, filename)

        columns = [{"name": i, "id": i} for i in df_geo.columns]

        data = df_geo.to_dict('records')

        # Debug
        print(df_geo.head())

        return data, columns

    else:

        return start_table_df.to_dict('records'), [{'id': '', 'name': ''}]

if __name__ == '__main__':
    app.run_server(debug=True, port=8000)
rp2532 commented 4 years ago

@montovaneli Confirming, that indeed works!

OliverSieweke commented 4 years ago

Same issue here and @montovaneli's workaround did the trick as well. Thanks!

cyprienc commented 3 years ago

Just to provide more color on this. It appears that having columns set to an empty list on the examples above will reproduce this error. On the other hand data can be set to an empty list.