Reputation: 115
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
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
Upvotes: 2
Views: 3049
Reputation: 2951
Here is another possible solution/approach.
dash.dcc.Store
to save different "shoppers"' grocery lists in browser memory storage cacheimport pandas as pd
from collections import OrderedDict
from dash import Dash, Input, Output, State, dcc, html, callback
from dash.exceptions import PreventUpdate
from dash import dash_table
names = ["Anna", "Bob", "John", "Robert", "Sarah"]
shoppers_df = pd.DataFrame(
OrderedDict(
[
("Name", names),
("Item", [""] * len(names)),
("Amount", [""] * len(names)),
]
)
).set_index("Name")
empty_shopping_list = (
pd.DataFrame(columns=["Name", "Item", "Amount"])
.set_index("Name")
.to_dict("records")
)
items = [
"Apple",
"Avocados",
"Baking powder",
"Balsamic vinegar",
"Banana",
"Breadcrumbs",
"Carrots",
"Chicken",
"Chili Powder",
"Cilantro",
"Crushed Red Pepper",
"Diced Tomatoes",
"Eggs",
"Ground Beef",
"Juice Concentrate",
"Milk",
"Parsley",
"Pizza or Other Convenience Meals",
"Sliced Turkey",
"Tortillas",
"Vegetable/Canola Oil",
"White Vinegar",
]
app = Dash(__name__)
app.layout = html.Div(
[
dcc.Store(id="session", storage_type="session"),
html.Div(
html.H1("Multi-Shopper Dynamic Grocery Lists"),
style={"text-align": "center"},
),
html.Hr(),
html.H2("Shopper", style={"text-align": "center"}),
dcc.Dropdown(
id="filter-dropdown",
options=[{"label": name, "value": name} for name in names],
placeholder="-Select a Person-",
multi=False,
value=None,
),
html.H3("Add new item to list:", style={"text-align": "center"}),
html.Div(
[
dcc.Input(id="add-items-num", type="number",),
html.Button("Add", id="add-items", n_clicks=0),
],
style={"text-align": "center"},
),
dash_table.DataTable(
id="table",
columns=[
{"id": "Item", "name": "Item", "presentation": "dropdown"},
{"id": "Amount", "name": "Amount", "type": "numeric"},
],
data=empty_shopping_list,
editable=True,
dropdown_conditional=[
{
"if": {"column_id": "Item",},
"options": [{"label": i, "value": i} for i in items],
},
],
style_cell={
"fontSize": "0.8rem",
"whiteSpace": "normal",
"padding": "3px",
"textAlign": "center",
"maxWidth": "300px",
},
style_header={
"fontWeight": "500",
"fontSize": "0.8rem",
"cursor": "pointer",
},
),
html.Div(id="table-container"),
html.Br(),
html.Div(
[
html.Button(
"Save", id="submit-save", style={"text-align": "center"}
)
],
style={"text-align": "center"},
),
]
)
@callback(
Output("table", "data", allow_duplicate=True),
Output("session", "data"),
Input("filter-dropdown", "value"),
Input("submit-save", "n_clicks_timestamp"),
State("table", "data"),
State("table", "columns"),
State("session", "data"),
prevent_initial_call=True,
)
def display_table(name, save, rows, columns, session):
if not session:
print("Initializing browser storage session...")
session = {"current_shopper": name, "last_save": 0}
if not name:
session["current_shopper"] = "None"
return [empty_shopping_list, session]
df = pd.DataFrame(rows, columns=[c["name"] for c in columns])
if save:
if save > session["last_save"]:
print(f"Saving: {save}")
session[name] = df.to_dict()
session["last_save"] = save
if session["current_shopper"] != name:
session["current_shopper"] = name
if name in session.keys():
df = pd.DataFrame.from_dict(session[name])
else:
if len(rows) < 1:
print(f"Creating new shopping list table for {name}")
df = pd.DataFrame(shoppers_df.loc[name]).T
else:
df = pd.concat([df, shoppers_df.loc[name]])
return [df.to_dict("records"), session]
@callback(
Output("table", "data", allow_duplicate=True),
Input("add-items", "n_clicks"),
State("add-items-num", "value"),
State("table", "data"),
State("table", "columns"),
prevent_initial_call=True,
)
def add_rows(n_clicks, n_new_rows, rows, columns):
if n_clicks > 0:
df = pd.DataFrame(rows, columns=[c["name"] for c in columns])
df = pd.concat(
[
df,
pd.DataFrame(
[["", ""]] * n_new_rows, columns=["Item", "Amount"]
),
]
)
return df.to_dict("records")
else:
raise PreventUpdate
@callback(
Output("table", "data"),
Input("table", "data_timestamp"),
State("table", "data"),
State("table", "columns"),
State("session", "data"),
)
def update_table(timestamp, rows, columns, session):
df = pd.DataFrame(rows, columns=[c["name"] for c in columns])
return df.to_dict("records")
if __name__ == "__main__":
app.run(debug=True)
dash_table
,Upvotes: 0
Reputation: 115
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)
Upvotes: 1