How do I arrange and code data for a Dash plotly editable datatable?

I am brand new to all of this (and I have a cold), so I apologize up front if this seems dense.

I have spent this week trying to figure out how to design an editable datatable via Flask without much success. I found the plugin bootstable, and I had my table ready to go, with the data populating the cells perfectly. But I could not pip install jquery (required) and its dependencies. This appears to be a common problem, evidenced by the posts I saw online.

So I circled back to the editable Dash/plotly datatables.

My problem here is that I cannot figure out how to populate the cells with the data I need to use.

I pasted the code from one the Dash editable templates below as an example, as well as my best attempt to make it work. Changing the columns is easy. And the code works fine with the dummy data, but I cannot figure out how to make my data (currently in JSON form ) compatible with this template. I have poured over the Dash/Plotly website and I don’t see much direction on this topic. I could be wrong, but I made a very honest effort.

How would arrange my data so that it can be used by data=[ ] in the code below?

My data is a list of Python dictionaries currently in JSON form, and sent as a simple list via Flask to index.html. I know it transfers because it works in other unsuccessful attempts. I just can’t install the dependencies for jquery, as noted above. I could easily transform it into a CSV file if necessary, though I would prefer JSON.

Does anyone know how to resolve this issue?

Any help at this point would be greatly appreciated.

Here is the template online, found on the Editable Tables page:

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

app = dash.Dash(__name__)

params = [
    'Weight', 'Torque', 'Width', 'Height',
    'Efficiency', 'Power', 'Displacement'
]

app.layout = html.Div([
    dash_table.DataTable(
        id='table-editing-simple',
        columns=(
            [{'id': 'Model', 'name': 'Model'}] +
            [{'id': p, 'name': p} for p in params]
        ),
        data=[
            dict(Model=i, **{param: 0 for param in params})
            for i in range(1, 5)
        ],
        editable=True
    ),
    dcc.Graph(id='table-editing-simple-output')
])


@app.callback(
    Output('table-editing-simple-output', 'figure'),
    Input('table-editing-simple', 'data'),
    Input('table-editing-simple', 'columns'))
def display_output(rows, columns):
    df = pd.DataFrame(rows, columns=[c['name'] for c in columns])
    return {
        'data': [{
            'type': 'parcoords',
            'dimensions': [{
                'label': col['name'],
                'values': df[col['id']]
            } for col in columns]
        }]
    }


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

Here is my attempt to make it work, though I receive an “Error loading layout” message, where user_edits is the list of dictionaries and each_dict is each dictionary in the list.

Thank you for any sincere feedback.

app = dash.Dash(__name__)

with open("./json/flask_dict.json", "r") as flask_dicts:
   user_edits = json.load(flask_dicts)

app.layout = html.Div([
    dash_table.DataTable(
        id='table-editing-simple',
        columns=(
            [{'id': 'Index', 'name': 'Index'}] +
            [{'id': 'Amounts', 'name': 'Amounts'}] +
            [{'id': 'Modifiers', 'name': 'Modifiers'}] +
            [{'id': 'Units', 'name': 'Units'}] +
            [{'id': 'Ings', 'name': 'Ings'}]            
        ),
        data=[{v for k,v in each_dict.items()} for each_dict in user_edits],
        editable=True
    ),
    dcc.Graph(id='table-editing-simple-output')
])

Here is the list of dictionaries in their present form:
recipe_ents_list = [{‘Index’: 0, ‘Amounts’: ‘.5’, ‘Modifiers’: ‘None’, ‘Units’: ‘teaspoon’, ‘Ings’: ‘dried oregano’},
{‘Index’: 1, ‘Amounts’: ‘0.25’,‘Modifiers’: ‘None’, ‘Units’: ‘tsp’, ‘Ings’: ‘red chilli flakes’},
{‘Index’: 2, ‘Amounts’: ‘0.25’, ‘Modifiers’: ‘None’, ‘Units’: ‘tsp’, ‘Ings’: ‘ground cloves’},
{‘Index’: 3, ‘Amounts’: ‘1’, ‘Modifiers’: ‘None’, ‘Units’: ‘tbsp’, ‘Ings’: ‘sunflower oil’},
{‘Index’: 4, ‘Amounts’: ‘1’, ‘Modifiers’: ‘None’, ‘Units’: ‘tsp’, ‘Ings’: ‘mustard seeds’},
{‘Index’: 5, ‘Amounts’: ‘1’, ‘Modifiers’: ‘divided’, ‘Units’: ‘tsp’, ‘Ings’: ‘salt’},
{‘Index’: 6, ‘Amounts’: ‘1.33’, ‘Modifiers’: ‘None’, ‘Units’: ‘tsp’, ‘Ings’: ‘cumin’},
{‘Index’: 7, ‘Amounts’: ‘1.5’, ‘Modifiers’: ‘None’, ‘Units’: ‘teaspoon’, ‘Ings’: ‘dried thyme’},
{‘Index’: 8, ‘Amounts’: ‘10’, ‘Modifiers’: ‘None’, ‘Units’: ‘teaspoon’, ‘Ings’: ‘cardamom pods’},
{‘Index’: 9, ‘Amounts’: ‘3’, ‘Modifiers’: ‘None’, ‘Units’: ‘cm’, ‘Ings’: ‘ginger’},
{‘Index’: 10, ‘Amounts’: ‘3’, ‘Modifiers’: ‘medium’, ‘Units’: ‘cm’, ‘Ings’: ‘shallots’},
{‘Index’: 11, ‘Amounts’: ‘300’, ‘Modifiers’: ‘None’, ‘Units’: ‘grams’, ‘Ings’: ‘red lentils’},
{‘Index’: 12, ‘Amounts’: ‘4’, ‘Modifiers’: ‘minced’, ‘Units’: ‘grams’, ‘Ings’: ‘cloves of garlic’},
{‘Index’: 13, ‘Amounts’:‘400’, ‘Modifiers’: ‘None’, ‘Units’: ‘grams’, ‘Ings’: ‘diced tomatoes’},
{‘Index’: 14, ‘Amounts’: ‘80’, ‘Modifiers’: ‘None’, ‘Units’: ‘grams’, ‘Ings’: ‘baby spinach’},
{‘Index’: 15, ‘Amounts’: ‘1’, ‘Modifiers’: ‘None’, ‘Units’: ‘handful’,‘Ings’: ‘cilantro’},
{‘Index’: 16, ‘Amounts’: ‘1’, ‘Modifiers’: ‘Half’, ‘Units’: ‘handful’, ‘Ings’: ‘lemon’}]

Hi @robertpfaff and welcome to the Dash community :slightly_smiling_face:

The good news is that your data is already in the format required for the Dash DataTable.

I’m not sure about how you are loading the data, but here is an example of how to use the sample data you provided:


from dash import Dash, dcc, html, dash_table

recipe_ents_list = [{'Index': 0, 'Amounts': '.5', 'Modifiers': 'None', 'Units': 'teaspoon', 'Ings': 'dried oregano'},
{'Index': 1, 'Amounts': '0.25','Modifiers': 'None', 'Units': 'tsp', 'Ings': 'red chilli flakes'},
{'Index': 2, 'Amounts': '0.25', 'Modifiers': 'None', 'Units': 'tsp', 'Ings': 'ground cloves'},
{'Index': 3, 'Amounts': '1', 'Modifiers': 'None', 'Units': 'tbsp', 'Ings': 'sunflower oil'},
{'Index': 4, 'Amounts': '1', 'Modifiers': 'None', 'Units': 'tsp', 'Ings': 'mustard seeds'},
{'Index': 5, 'Amounts': '1', 'Modifiers': 'divided', 'Units': 'tsp', 'Ings': 'salt'},
{'Index': 6, 'Amounts': '1.33', 'Modifiers': 'None', 'Units': 'tsp', 'Ings': 'cumin'},
{'Index': 7, 'Amounts': '1.5', 'Modifiers': 'None', 'Units': 'teaspoon', 'Ings': 'dried thyme'},
{'Index': 8, 'Amounts': '10', 'Modifiers': 'None', 'Units': 'teaspoon', 'Ings': 'cardamom pods'},
{'Index': 9, 'Amounts': '3', 'Modifiers': 'None', 'Units': 'cm', 'Ings': 'ginger'},
{'Index': 10, 'Amounts': '3', 'Modifiers': 'medium', 'Units': 'cm', 'Ings': 'shallots'},
{'Index': 11, 'Amounts': '300', 'Modifiers': 'None', 'Units': 'grams', 'Ings': 'red lentils'},
{'Index': 12, 'Amounts': '4', 'Modifiers': 'minced', 'Units': 'grams', 'Ings': 'cloves of garlic'},
{'Index': 13, 'Amounts':'400', 'Modifiers': 'None', 'Units': 'grams', 'Ings': 'diced tomatoes'},
{'Index': 14, 'Amounts': '80', 'Modifiers': 'None', 'Units': 'grams', 'Ings': 'baby spinach'},
{'Index': 15, 'Amounts': '1', 'Modifiers': 'None', 'Units': 'handful','Ings': 'cilantro'},
{'Index': 16, 'Amounts': '1', 'Modifiers': 'Half', 'Units': 'handful', 'Ings': 'lemon'}]


app = Dash(__name__)

app.layout = html.Div([
    dash_table.DataTable(
        id='table-editing-simple',
        columns=[{'id': i, 'name':i} for i in ['Index', 'Amounts', 'Modifiers', 'Units', 'Ings']],
        data=recipe_ents_list,
        editable=True
    ),
    dcc.Graph(id='table-editing-simple-output')
])

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


image

1 Like

Awesome. Thank you so much. I figured I was over-thinking it. I really appreciate the help, you saved me a lot of hair-pulling and gnashing of teeth. Thank you. Thank you. Thank you.

1 Like

I have a follow-up question if you don’t mind. I’m not finding the answer on YouTube or other sources as I expected. After the user has updated the information in the editable data table, how would the user submit the changes, in my case preferably as changes to the original JSON object that populates the table in the first place?

I am assuming there is a way to include a submit button that triggers a “Json dump” function of some kind, but I would love to know what you consider to be the best approach…?

In your response, you mentioned that you were not sure how I was “loading the data.” The data is a list of Python dictionaries derived from a JSON object and opened and loaded in the function where the table is created. Flask is the web framework. I hope this answers your question in case it is relevant to my follow-up question.

Again, I think I just need a simple submit button, but is there a better way?

Many thanks,

Robert

HI @robertpfaff

Below is a minimal example of how to access the data in the table data with a callback.

There isn’t a built-in function for finding the changes. You have to do your own data wrangling for that.


from dash import Dash, dcc, html, dash_table, Input, Output, State

app = Dash(__name__)

recipe_ents_list = [{'Index': 0, 'Amounts': '.5', 'Modifiers': 'None', 'Units': 'teaspoon', 'Ings': 'dried oregano'},
{'Index': 1, 'Amounts': '0.25','Modifiers': 'None', 'Units': 'tsp', 'Ings': 'red chilli flakes'},
{'Index': 2, 'Amounts': '0.25', 'Modifiers': 'None', 'Units': 'tsp', 'Ings': 'ground cloves'},
{'Index': 3, 'Amounts': '1', 'Modifiers': 'None', 'Units': 'tbsp', 'Ings': 'sunflower oil'},
{'Index': 4, 'Amounts': '1', 'Modifiers': 'None', 'Units': 'tsp', 'Ings': 'mustard seeds'},
{'Index': 5, 'Amounts': '1', 'Modifiers': 'divided', 'Units': 'tsp', 'Ings': 'salt'},
{'Index': 6, 'Amounts': '1.33', 'Modifiers': 'None', 'Units': 'tsp', 'Ings': 'cumin'},
{'Index': 7, 'Amounts': '1.5', 'Modifiers': 'None', 'Units': 'teaspoon', 'Ings': 'dried thyme'},
{'Index': 8, 'Amounts': '10', 'Modifiers': 'None', 'Units': 'teaspoon', 'Ings': 'cardamom pods'},
{'Index': 9, 'Amounts': '3', 'Modifiers': 'None', 'Units': 'cm', 'Ings': 'ginger'},
{'Index': 10, 'Amounts': '3', 'Modifiers': 'medium', 'Units': 'cm', 'Ings': 'shallots'},
{'Index': 11, 'Amounts': '300', 'Modifiers': 'None', 'Units': 'grams', 'Ings': 'red lentils'},
{'Index': 12, 'Amounts': '4', 'Modifiers': 'minced', 'Units': 'grams', 'Ings': 'cloves of garlic'},
{'Index': 13, 'Amounts':'400', 'Modifiers': 'None', 'Units': 'grams', 'Ings': 'diced tomatoes'},
{'Index': 14, 'Amounts': '80', 'Modifiers': 'None', 'Units': 'grams', 'Ings': 'baby spinach'},
{'Index': 15, 'Amounts': '1', 'Modifiers': 'None', 'Units': 'handful','Ings': 'cilantro'},
{'Index': 16, 'Amounts': '1', 'Modifiers': 'Half', 'Units': 'handful', 'Ings': 'lemon'}]

app.layout = html.Div([
    html.Button("Submit", id="submit"),
    html.Div(id="msg"),
    dash_table.DataTable(
        id='table-editing-simple',
        columns=[{'id': i, 'name':i} for i in ['Index', 'Amounts', 'Modifiers', 'Units', 'Ings']],
        data=recipe_ents_list,
        editable=True
    ),
    dcc.Graph(id='table-editing-simple-output')
])

@app.callback(
    Output("msg", "children"),
    Input("submit", "n_clicks"),
    State("table-editing-simple", "data"),
    prevent_initial_call=True
)
def save_data(n, data):
    # do something with the data
    print(data)
    return "Data Submitted"


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


1 Like

Can’t thank you enough, @AnnMarieW. This was the last hurdle I needed to clear before I finished the app.

I am fairly certain I understand this now, but to be doubly sure. N is the number of changes made by the user. Data is just the same list of Python dictionaries with the user’s changes. Since I want it in JSON form, I just need to open a json_file for writing, then incorporate json.dump(data,json_file,indent=4) and I have what I need to return a nutritional profile.

This was a big deal to me! I thank you greatly.

Hey @robertpfaff ,

Glad it worked for you!
The Input("submit", "n_clicks"), is what triggers the callback when the button is clicked.
The State("table-editing-simple", "data"), doesn’t trigger the callback, but it will provide the “state” of the table’s data property at the time the callback was triggered.

In the callback function, you need to provide a variable name for each of the Inputs (or States).
def save_data(n, data):

In your case you probably don’t need to use the number of times the button is clicked, but you still need to provide some variable. If you prefer, you could use a dummy variable such as an underscore: _
def save_data(_, data):

And to be clear, the data in the callback is not a list of changes only. It’s all of the table data at the time the callback was triggered. You will then have to compare it to the original data to find the changes.

You can find lots of good info on callbacks in the tutorial here: Part 3. Basic Callbacks | Dash for Python Documentation | Plotly
and also here: Advanced Callbacks | Dash for Python Documentation | Plotly

1 Like

Perfect. Thank you again.