'Python Dash Datatable Input

I am new to Python Dash, so I am not sure what is possible. What I would like to accomplish is making a datatable where the user is able to manipulate data in the datatable which would result in changes for other graphs.

Here is an example of what I am trying to do:

I have information about people’s grocery lists and I would like to be able to change what is in their list

The data that I have is structured as such

Name Item Amount
Bob Apple 1
Bob Banana 2
Anna Apple 2
Anna Banana 1

I would like to be able to have a datatable where there is a drop down to select the person’s list so the datatable would just have their items and amounts. In the selected datatable, they should be able to add and delete rows, change the item with a dropdown in the cell, and change the amount by typing in the desired amount.

I have been able to create this somewhat where you can select a person’s list, and the item can be changed based on a list of foods given, but I cannot figure out how to do the rest. Here is what I have been able to put together for my example

from dash import dash_table as dt
from dash import dcc
from dash import html
from dash.dependencies import Input
from dash.dependencies import Output
import pandas as pd

df = pd.DataFrame({'Name' : ['Bob', 'Bob', 'Anna', 'Anna'],
                   'Item' : ['Apple', 'Banana', 'Apple', 'Banana'],
                   'Amount' : [1, 2, 2, 1]})

app = dash.Dash(__name__)

names = df['Name'].unique().tolist()
app.layout = html.Div(
    children=[
        dcc.Dropdown(
            id="filter_dropdown",
            options=[{"label": name, "value": name} for name in names],
            placeholder="-Select a Person-",
            multi=False,
            value=df['Name'].values,
        ),
        dt.DataTable(
            id="table-container",
            columns=[{'name': 'Item', 'id': 'Item', 'presentation':'dropdown'},
                     {'name': 'Amount', 'id': 'Amount'}],
            data=df.to_dict("records"),
            editable=True,
            dropdown={
                'Item' : {
                    'options': [
                        {'label': i, 'value': i}
                        for i in list(df['Item'].unique())
                        ]
                    }
                }
        )
    ]
)
@app.callback(
    Output("table-container", "data"), 
    Input("filter_dropdown", "value"))
def display_table(name):
    dff = df[df['Name'].isin([name])]
    return dff.to_dict("records")
    

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

Here is what it looks like in the browser enter image description here

I can select an item from the dropdown in the cell and it will appear to change it, but when I switch between people it loses its change that I made. I also cannot change the amounts. The next step would be is if I can use all the data as well as the changes in the data in another graph (for example to show how many people have a certain item). If someone would be able to help, I would appreciate it.

Thanks



Solution 1:[1]

After doing some work, I do have it working. It is by means not the optimal or right way to do this probably, but it does what I want it to do. I ended up using a global variable for my data frame and whenever there was a change to the data table, I would filter out the data from the data frame and add the modified data from the data table.

To get a visual to work, I listen for changes to the data table. I know that there is probably a race between updating the global variable and returning the new visual, but I did not have to delay the visual being updated.

Below is the code that I came up with. I hope this helps others somehow if they wanted to make a simple "spreadsheet" that people can manipulate and result in a responsive visual related to the data.

import dash
import pandas as pd
from dash import dash_table as dt
from dash import dcc
from dash import html
from dash.dependencies import Input
from dash.dependencies import Output
import plotly.express as px

df = pd.DataFrame({'Name' : ['Bob', 'Bob', 'Anna', 'Anna'],
                   'Item' : ['Apple', 'Banana', 'Apple', 'Banana'],
                   'Amount' : [1, 2, 2, 1]})

app = dash.Dash(__name__)

names = df['Name'].unique().tolist()
app.layout = html.Div(
    children=[
        dcc.Dropdown(
            id="filter_dropdown",
            options=[{"label": name, "value": name} for name in names],
            placeholder="-Select a Person-",
            multi=False,
            value=df['Name'].values,
        ),
        dt.DataTable(
            id="table-container",
            columns=[{'name': 'Item', 'id': 'Item', 'presentation':'dropdown'},
                     {'name': 'Amount', 'id': 'Amount'}],
            data=df.to_dict("records"),
            editable=True,
            row_deletable=True,
            dropdown={
                'Item' : {
                    'options': [
                        {'label': i, 'value': i}
                        for i in list(df['Item'].unique())
                        ]
                    }
                }
        ),
        html.Button('Add', id='add_btn', n_clicks = 0),
        dcc.Graph(id='visual')
    ]
)
@app.callback(
    Output("table-container", "data"), 
    Input("filter_dropdown", "value"),
    Input("table-container", "data"),
    Input("table-container", "columns"),
    Input("add_btn", 'n_clicks'))
def display_table(name, rows, columns, n_clicks):
    e = dash.callback_context.triggered[0]['prop_id']
    global df
    if e in ["table-container.data", "table-container.columns"]:
        temp = pd.DataFrame(rows, columns=[c['name'] for c in columns])
        temp['Name'] = name
        df = df[~df['Name'].isin([name])]
        df = df.append(temp)
    elif e == 'add_btn.n_clicks':
        if n_clicks > 0:
            df = df.append(pd.DataFrame({'Name':name, 'Item': '', 'Amount': 0}, index=[0]))
    dff = df[df['Name'].isin([name])].to_dict("records")
    return dff
    
@app.callback(Output('visual', 'figure'),
              Input("table-container", "data"),
              Input("table-container", "columns"))
def display_graph(rows, columns):
    fig = px.bar(df, x='Item', y='Amount')
    return fig

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

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1