Hi!
I’m having trouble structuring callbacks in my dash such that users are able to edit an uploaded DataTable. I have a test example below, which is a barely-modified version of the dcc.Upload example, where I intialize an empty DataTable with the columns I want (“in1”,“in2”, and “out”), and callbacks such that when the DataTable is edited, it is updated so that “out” is the result of adding “in1” and “in2” (row-wise).
Ideally the user should be able to upload an excel or csv file that looks like this:
in1 | in2 | out | |
10 | 15 | NA |
And see a table that looks like this:
in1 | in2 | out | |
10 | 15 | 25 |
Which updates the “out” column whenever the user changes a value in “in1” or “in2”.
My first idea was to listen to the State
of the upload’s contents
component, and take the timestamps from both the DataTable and the upload widget as inputs. When the upload widget gets a new file, it would trigger throwing the uploaded data into the DataTable, and then clear the contents of the upload widget. Then, if the user changes the DataTable, the callback would see that the contents of the upload widget are None
, and just update the existing data. However, I keep getting
Invalid argument `data[0]` passed into DataTable with ID "output-data-upload".
Expected `object`.
Was supplied type `array`.
when I try this. Is there some much easier way of doing this that I’m missing completely? Full code below (ignore empty second tab).
import dash
import dash_core_components as dcc
import dash_html_components as html
from dash.dependencies import Input, Output, State
import dash_table
import base64
import pandas as pd
import numpy as np
external_stylesheets = ['https://codepen.io/chriddyp/pen/bWLwgP.css']
app = dash.Dash(__name__, external_stylesheets=external_stylesheets)
app.layout = html.Div([
dcc.Tabs([
dcc.Tab(label='Main tab', children=[
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=True
),
html.Div(dash_table.DataTable(
id='output-data-upload',
data=[],
columns=[{'name': i, 'id': i} for i in ['in1','in2','out']],
editable=True))
]),
dcc.Tab(label='Tab2', children=[
dcc.Input(id='string',type='text',debounce=True)
])
])
])
def parse_contents(contents, filename, date):
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 html.Div([
'There was an error processing this file.'
])
return df.to_dict('records')
@app.callback([Output('output-data-upload', 'data'),
Output('upload-data','contents')],
[Input('upload-data', 'last_modified'),
Input('output-data-upload','data_timestamp')],
[State('upload-data', 'filename'),
State('upload-data', 'contents'),
State('output-data-upload','data')])
def update_output(upload_times, data_time, filenames, upload_contents, data_rows):
if upload_contents is not None:
data = [parse_contents(c, n, d) for c, n, d in zip(upload_contents, filenames, upload_times)]
return data, None
else:
for row in data_rows:
row['out'] = float(row['in1']) + float(row['in2'])
return data_rows, None
if __name__ == '__main__':
app.run_server(debug=True)