Dynamic pattern matching with MATCH failing - python

I'm creating a dashboard with Dash on which I want a variable number of graphs with associated dropdowns underneath each other. The dropdowns control an aspect of the graph (how it's sorted, but this is unimportant). Here is the code:
from dash import html, dcc
from dash.dependencies import Output, Input, State, MATCH
import dash_bootstrap_components as dbc
from app.plots import get_product_breakdown_bar_chart
from .selectors import get_product_selection_checklist, get_impact_parameter_selection_checklist, get_product_to_sort_on_dropdown, DEFAULT_PRODUCT_CHECKLIST_ID, DEFAULT_IMPACT_PARAMETER_CHECKLIST_ID, DEFAULT_PRODUCTS, DEFAULT_IMPACT_PARAMETERS
def get_selectors_pane():
selectors_row = dbc.Row([
dbc.Col(
[get_product_selection_checklist()],
width = 6
),
dbc.Col(
[get_impact_parameter_selection_checklist()],
width = 6
)
])
labels_row = dbc.Row([
dbc.Col(
[dbc.Label("Products:", html_for = DEFAULT_PRODUCT_CHECKLIST_ID)],
width = 6
),
dbc.Col(
[dbc.Label("Impact parameters: ", html_for = DEFAULT_IMPACT_PARAMETER_CHECKLIST_ID)],
width = 6
)
])
return html.Div([
labels_row,
selectors_row
])
saved_sorted_on_states = {}
selected_products = DEFAULT_PRODUCTS
def render_graph_rows(selected_products, selected_impact_parameters):
def sov(impact_parameter):
if impact_parameter in saved_sorted_on_states:
if saved_sorted_on_states[impact_parameter] in selected_products:
return saved_sorted_on_states[impact_parameter]
else:
saved_sorted_on_states.pop(impact_parameter)
return selected_products[0]
else:
return selected_products[0]
rows = []
for s_ip in selected_impact_parameters:
sort_on_dropdown_id = {"type": "sort-on-dropdown", "index": s_ip}
ip_graph_id = {"type": "impact-parameter-graph", "index": s_ip}
rows.append(
html.Div([
dbc.Row([
dbc.Col([dbc.Label("Sort on:", html_for = sort_on_dropdown_id)], width = 2),
dbc.Col([get_product_to_sort_on_dropdown(sort_on_dropdown_id, sov(s_ip))], width = 10)
]),
dbc.Row([
dbc.Col([
dcc.Graph(
id = ip_graph_id,
figure = get_product_breakdown_bar_chart(s_ip, selected_products, sov(s_ip))
)
], width = 12)
])
])
)
return rows
content_layout = html.Div(
id = "content",
children = [
get_selectors_pane(),
html.Div(
id = "graph-grid",
children = render_graph_rows(DEFAULT_PRODUCTS, DEFAULT_IMPACT_PARAMETERS)
)
],
style = {
"margin-left": "14rem",
"margin-right": "2rem",
"padding": "2rem 1rem",
}
)
def register_callback(app):
def sort_graph_callback(value, index):
global saved_sorted_on_states
saved_sorted_on_states[index] = value
return (get_product_breakdown_bar_chart(index, selected_products, value), )
app.callback(
[Output({"type": "impact-parameter-graph", "index": MATCH}, "figure")],
[Input({"type": "sort-on-dropdown", "index": MATCH}, "value")],
[State({"type": "sort-on-dropdown", "index": MATCH}, "id")]
)(sort_graph_callback)
def new_master_selection_callback(s_ps, s_ips):
global selected_products
selected_products = s_ps
return (render_graph_rows(s_ps, s_ips), )
app.callback(
[Output("graph-grid", "children")],
[Input(DEFAULT_PRODUCT_CHECKLIST_ID, "value"), Input(DEFAULT_IMPACT_PARAMETER_CHECKLIST_ID, "value")]
)(new_master_selection_callback)
The problem is that the sort_graph_callback defined on line 86 never gets called. This callback is supposed to connect dynamically added graphs with dynamically added dropdowns associated to them. But when I select a different option in such a dropdown nothing happens to the associated graph and the callback doesn't get called at all. I know this from setting breakpoints in them. I have verified that the correct id's are assigned to the rendered graph and dropdown components.
(Please note that I'm registering the callbacks in a peculiar way due to code organization reasons. I have verified that this is not the cause of the issue)
I have no clue anymore how to debug this issue. In my development environment pattern matching callback examples from the official documentation work just fine. Is there anything I'm missing?
Thank you so much in advance,
Joshua

Related

Python dash return several values inside for loop

For my dash app, in order to update some graphs dynamically, I have to use a function that I named update_graphs inside a for loop. Some of the graphs contain several traces while some others only have one. The update_graphs function is called inside a callback and returns a dict and an int to update the extendData property of the graph object. However, since I am using a return statement inside a for loop, I only get the first trace.
I am not familiar with the generators and the yield keyword, maybe this is an option. But I haven't been able to make it work.
I have also tried to store the results of the update_graphs inside a list but it is not working.
Any help is appreciated!
Here is the code for the app:
import dash
from dash.dependencies import Output, Input, State, MATCH, ALL
from dash import dcc, html, ctx
import plotly
import plotly.express as px
import random
import plotly.graph_objs as go
import pandas as pd
# Initializing the data with the correct format
init_store = {}
n=3
init_df = pd.DataFrame({'a':pd.Series(dtype='int'), 'b':pd.Series(dtype='int'), 'c':pd.Series(dtype='int'), 'd':pd.Series(dtype='int')}, index=range(50))
init_df['a'] = init_df.index
init_store['0'] = init_df
for i in range(n):
init_df = pd.DataFrame({'a':pd.Series(dtype='int'), 'b':pd.Series(dtype='int')}, index=range(50))
init_df['a'] = init_df.index
init_store[f'{i+1}'] = init_df
# Function to update the dataframes with the new observations
def get_data(json_data):
df = pd.read_json(json_data)
compteur = df['a'][len(df['a'])-1]
if len(df.columns) > 2:
new_row = {'a':compteur + 1, 'b':random.randint(13,26), 'c':random.randint(13,26), 'd':random.randint(13,26)}
else:
new_row = {'a':compteur + 1, 'b':random.randint(13,26)}
df = df.shift(periods=-1)
df.iloc[len(df)-1] = new_row
return(df.to_json())
# Function to update the graphs based on the dataframes
def update_graphs(json_data, column, index=0):
df = pd.read_json(json_data)
nb_obs = df.shape[0]
x_new = df['a'][len(df)-1]
y_new = df[column][nb_obs-1]
return dict(x=[[x_new]], y=[[y_new]]), index
colors = px.colors.qualitative.G10
def generate_graph_containers(index, json_data):
dataframe = pd.read_json(json_data)
X = dataframe['a']
Y = dataframe.loc[:, dataframe.columns != 'a']
graph_id = {'type': 'graph-', 'index': index}
return(
html.Div(
html.Div(
dcc.Graph(
id=graph_id,
style={"height": "8rem"},
config={
"staticPlot": False,
"editable": False,
"displayModeBar": False,
},
figure=go.Figure(
{
"data": [
{
"x": list(X),
"y": list(Y[Y.columns[i]]),
"mode": "lines",
"name": Y.columns[i],
"line": {"color": colors[i+2]},
}
for i in range(len(Y.columns))
],
"layout": {
"uirevision": True,
"margin": dict(l=0, r=0, t=4, b=4, pad=0),
"xaxis": dict(
showline=False,
showgrid=False,
zeroline=False,
showticklabels=False,
),
"yaxis": dict(
showline=False,
showgrid=False,
zeroline=False,
showticklabels=False,
),
"paper_bgcolor": "rgba(0,0,0,0)",
"plot_bgcolor": "rgba(0,0,0,0)",
}
}
)
)
)
)
)
app = dash.Dash(__name__)
store = [dcc.Store(id={'type':'store-', 'index':i}, data=init_store[str(i)].to_json()) for i in range(n)]
def make_layout():
return(
html.Div(
[
html.Div(
store
),
dcc.Interval(
id = 'interval',
interval = 1000,
n_intervals = 0
),
html.Div(
[
generate_graph_containers(str(i), store[i].data) for i in range(n)
]
)
]
)
)
app.layout = make_layout
#app.callback(
Output(component_id={'type':'store-', 'index':MATCH}, component_property='data'),
[
Input('interval', 'n_intervals'),
State(component_id={'type':'store-', 'index':MATCH}, component_property='data')
]
)
def update_data(time, data):
return(get_data(data))
#app.callback(
Output(component_id={'type':'graph-', 'index':MATCH}, component_property='extendData'),
Input(component_id={'type':'store-', 'index':MATCH}, component_property="data")
)
def update_graphs_callback(data):
triggered_id = ctx.triggered_id
print(triggered_id['index'])
columns = ['b', 'c', 'd']
if triggered_id['index'] == 0:
for i in range(len(columns)):
return(update_graphs(data, columns[i], i))
else:
return(update_graphs(data, 'b'))
if __name__ == '__main__':
app.run_server(debug=True)
I figured it out. The trick is in the format expected to update the extendData property of a figure. When trying to update several traces, the format should be a dictionary with a key for the x values and one for the y values. The values associated should be an array for each key, containing an array per trace. Don't forget to add the trace indices after the dictionary. So for example, in the case of 3 distinct traces, the function should return something like:
dict(x=[[x_0], [x_1], [x_2]], y=[[y_0], [y_1], [y_2]]), [0, 1, 2]
Therefore the update_graphs function should be:
def update_graphs(json_data):
df = pd.read_json(json_data)
nb_obs = df.shape[0]
x_new = []
y_new = []
trace_index = []
for i in range(len(df.columns)-1):
x_new.append([df['a'][len(df)-1]])
y_new.append([df[df.columns[i+1]][nb_obs-1]])
trace_index.append(i)
return(dict(x=x_new, y=y_new), trace_index)
And the callback to update the graphs should be changed to:
#app.callback(
Output(component_id={'type':'graph-', 'index':MATCH}, component_property='extendData'),
Input(component_id={'type':'store-', 'index':MATCH}, component_property="data")
)
def update_graphs_callback(data):
return(update_graphs(data))

Plotly dash creating columns based on user selection

I am working on a selection menu where the user can select the number of experiments they want to compare/see. Depending on this number I would like to adapt the number of columns that appear, being one for each experiment. In the image below an example of the menu with one experiment can be seen. However, I would like these dropdowns to duplicate if the user selects two experiments having them in side-to-side columns.
Here is an example of one column and one experiment:
The code I have so far is the following:
# Load Data
df = px.data.tips()
external_stylesheets = ["https://codepen.io/chriddyp/pen/bWLwgP.css"]
# Build App
app = JupyterDash(__name__, external_stylesheets=external_stylesheets)
app.layout = html.Div([
# first row - selecting the number of experiments
html.Div([
html.I("Select the number of experiments you want to compare (min - 2 and max - 10):"),
html.Br(),
# input box for the number of experiments
dcc.Input(id="num_exp" , type='number', min = 2, max = 10, placeholder="No of experiments"),
html.Div(id="output")
], style = {'width': '100%'}
),
# second row - where the experiments are organized in columns
html.Div([
html.Label([
html.I("X axis "),
dcc.Dropdown(
id = 'dd_axis',
clearable = False,
placeholder = 'Select property x-axis',
options=[
{'label': i, 'value': i}
for i in properties
],
style={
'width': '100%'
})
]),
html.Label([
html.I("Y Axis "),
dcc.Dropdown(
id = 'dd_yaxis',
clearable = False,
placeholder = 'Select property y-axis',
options=[ ],
disabled = False)
])
], style = {'display': 'inline-block', 'vertical-align': 'top', 'margin-left': '3vw', 'margin-top': '3vw', 'width': '50%'})
])
#app.callback(
Output("output", "children"),
# number of experiments input
Input("num_exp", "value"),
)
def update_output(test_type):
in_testtype = test_type
return u'{} experiments'.format(test_type)
def cb_render(*vals):
return " | ".join((str(val) for val in vals if val))
Can you help me adding more dropdown menus to the side and dividing the second row in columns based in the user selection?
Thank you!
You can do this by using dbc.Col and dbc.Row.
In your layout, have a Div and within it an empty dbc.Row that will be used to populate its children attribute with columns.
Then, in the callback that is triggered by the input box value, have a loop that returns the number of columns you want, with the relevant code for the dbc.Col and what you'd like to display in it.
Here's a worked-up example of your original code:
import dash
from dash import dcc, html
import dash_bootstrap_components as dbc
from dash.dependencies import Input, Output
app = dash.Dash(external_stylesheets=[dbc.themes.BOOTSTRAP])
properties = ["a", "b", "c"]
app.layout = html.Div([
# first row - selecting the number of experiments
html.Div([
html.I(
"Select the number of experiments you want to compare (min - 2 and max - 10):"),
html.Br(),
# input box for the number of experiments
dcc.Input(id="num_exp", value=2, type='number', min=2,
max=10, placeholder="No of experiments"),
html.Div([
dbc.Row([], id="output")
])
], style={'width': '100%'}
),
])
#app.callback(
Output("output", "children"),
# number of experiments input
Input("num_exp", "value"),
)
def update_output(test_type):
columns_list = []
letters = "xyzabcdefg"
for i in range(test_type):
columns_list.append(
dbc.Col([
html.Label([
html.I(f"{letters[i].upper()} axis "),
dcc.Dropdown(
id=f'{letters[i]}_axis',
clearable=False,
placeholder=f'Select property {letters[i]}-axis',
options=[
{'label': i, 'value': i}
for i in properties
],
style={
'width': '100%'
})
])
])
)
return columns_list
if __name__ == "__main__":
app.run_server(debug=True)

Dynamically render tabs with python dash

Does someone know how I would dynamically render tabs based on a given input?
For example, if I put a number(like 4) in a text box and pressed submit, I'd want to have 4 tabs show up underneath with content(let's the tab number underneath). If put 2 I'd want 2 tabs. I think I use a for loop somewhere but not sure how to implement.
I've only been able to get to show a static number of tabs once submit is clicked to start off.
import dash
import dash_html_components as html
import dash_core_components as dcc
import dash_bootstrap_components as dbc
from dash.dependencies import Input, Output, State
app = dash.Dash()
input_group_Row = dbc.Row([ dbc.Col([
dbc.CardBody(
[
dbc.InputGroup(
[
dbc.InputGroupAddon("Enter number", addon_type="prepend"),
dbc.Input(id='integer',placeholder="Enter int"),
],className="mb-3",),
]),
]),
dbc.Col([
dbc.Button('Enter', color='primary',id='load', block=True,n_clicks=0),
])
])
app.layout = html.Div([input_group_Row, html.Div(id='output-content')], style = CONTENT_STYLE)
#app.callback(Output('output-content', 'children'),
[Input('load', 'n_clicks')],
[State('integer','value')],
)
def render_tabs(click1, integ):
output =""
ctx = dash.callback_context
action = ctx.triggered[0]['prop_id'].split('.')[0]
if action == 'load':
print(type(int(integ)))
output = integ
return dcc.Tabs(id='tab', value='tab1',children=[
dcc.Tab(label='Tab 1', value='tab1', children=[html.Div(output,style={'color': 'blue', 'fontSize': 140})]),
dcc.Tab(label='Tab 2', value='tab2', children=[html.Div(int(output)+3,style={'color': 'blue', 'fontSize': 140})])
])
You could use a list comprehension.
List comprehensions provide a concise way to create lists. Common applications are to make new lists where each element is the result of some operations applied to each member of another sequence or iterable, or to create a subsequence of those elements that satisfy a certain condition.
https://docs.python.org/3/tutorial/datastructures.html
Based on your example you could do something like this:
#app.callback(
Output("output-content", "children"),
[Input("load", "n_clicks")],
[State("integer", "value")],
)
def render_tabs(click1, integ):
output = ""
ctx = dash.callback_context
action = ctx.triggered[0]["prop_id"].split(".")[0]
if action == "load":
output = int(integ)
return dcc.Tabs(
id="tab",
value="tab1",
children=[
dcc.Tab(
label=f"Tab {num + 1}",
value=f"tab{num + 1}",
children=[html.Div(num)],
)
for num in range(output)
],
)
The same solution with a for loop could look like this:
#app.callback(
Output("output-content", "children"),
[Input("load", "n_clicks")],
[State("integer", "value")],
)
def render_tabs(click1, integ):
output = ""
ctx = dash.callback_context
action = ctx.triggered[0]["prop_id"].split(".")[0]
if action == "load":
output = int(integ)
tabs = []
for num in range(output):
tabs.append(
dcc.Tab(
label=f"Tab {num + 1}",
value=f"tab{num + 1}",
children=[html.Div(num)],
)
)
return dcc.Tabs(
id="tab",
value="tab1",
children=tabs,
)

Dash Python: Add date-picker and sidebar to dashboard

I have a sidebar and a date-picker in my dashboard built with Dash. However, I am unable to load the graph correctly based on the date range selected. The data source can be found in the link below, however, I changed the column "Year" values to dates in this format "YYYY-MM-DD".
Data Source
Help is much appreciated.
Output errors:
FileNotFoundError: [Errno 2] No such file or directory: 'iranian_students.csv'
ID not found in layout
Attempting to connect a callback Input item to component:
"date-range"
but no components with that id exist in the layout.
If you are assigning callbacks to components that are
generated by other callbacks (and therefore not in the
initial layout), you can suppress this exception by setting
`suppress_callback_exceptions=True`.
This ID was used in the callback(s) for Output(s):
students.figure
students.figure
ID not found in layout
Attempting to connect a callback Output item to component:
"students"
but no components with that id exist in the layout.
If you are assigning callbacks to components that are
generated by other callbacks (and therefore not in the
initial layout), you can suppress this exception by setting
`suppress_callback_exceptions=True`.
This ID was used in the callback(s) for Output(s):
students.figure
My code:
# To add a new cell, type '# %%'
# To add a new markdown cell, type '# %% [markdown]'
# %%
import dash
import dash_core_components as dcc
import dash_html_components as html
from dash.dependencies import Input, Output, State
from flask import Flask
import pandas as pd
from pandas import DataFrame
from pandas import json_normalize
import pymongo
from pymongo import MongoClient
import plotly.graph_objs as go
import plotly.offline as pyo
import plotly.express as px
import numpy as np
import pytz, time
from datetime import datetime, tzinfo, timezone, timedelta, date
import dash_bootstrap_components as dbc
import random
from sqlalchemy import create_engine
from plotly.subplots import make_subplots
# %%
# constants
df = pd.read_csv('Bootstrap\Side-Bar\iranian_students.csv')
localTimezone = pytz.timezone('Africa/Cairo')
datetimeNow = datetime.now(localTimezone)
# %%
app = dash.Dash(__name__, external_stylesheets=[dbc.themes.FLATLY],
meta_tags=[{'name': 'viewport',
'content': 'width=device-width, initial-scale=1.0'}]
)
# %%
def serve_layoutOnReload():
# styling the sidebar
SIDEBAR_STYLE = {
"position": "fixed",
"top": 0,
"left": 0,
"bottom": 0,
"width": "16rem",
"padding": "2rem 1rem",
"background-color": "#f8f9fa",
}
# padding for the page content
CONTENT_STYLE = {
"margin-left": "18rem",
"margin-right": "2rem",
"padding": "2rem 1rem",
}
sidebar = html.Div(
[
html.H2("Sidebar", className="display-4"),
html.Hr(),
html.P(
"Number of students per education level", className="lead"
),
dbc.Nav(
[
dbc.NavLink("Home", href="/", active="exact"),
dbc.NavLink("Page 1", href="/page-1", active="exact"),
dbc.NavLink("Page 2", href="/page-2", active="exact"),
],
vertical=True,
pills=True,
),
],
style=SIDEBAR_STYLE,
)
content = html.Div(id="page-content", children=[], style=CONTENT_STYLE)
web_layout = html.Div([
dcc.Location(id="url"),
sidebar,
content
])
return web_layout
app.layout = serve_layoutOnReload
# app.layout = html.Div([
# dcc.Location(id="url"),
# sidebar,
# content
# ])
# %%
#app.callback(
Output("page-content", "children"),
[Input("url", "pathname")]
)
def render_page_content(pathname):
if pathname == "/":
return [
html.H1('Kindergarten',
style={'textAlign':'center'}),
dbc.Container([
dbc.Row([
dbc.Col([
html.P("Date picker:", className='text-right font-weight-bold mb-4'),
],xs=12, sm=12, md=12, lg=5, xl=5),
dbc.Col([
dcc.DatePickerRange(id='date-range',
min_date_allowed=date(2020, 6, 1),
max_date_allowed=datetimeNow.date(),
start_date=datetimeNow.date() - timedelta(days=7),
end_date=datetimeNow.date(), ),
],xs=12, sm=12, md=12, lg=5, xl=5),], no_gutters=True, justify='center'),
dbc.Row(
dbc.Col(html.H2(" ", className='text-center text-primary mb-4'), width=12)
),
dbc.Row([
dbc.Col([
dcc.Graph(id="students", figure={}),
], xs=12, sm=12, md=12, lg=5, xl=5),], justify='center')
], fluid=True)
]
elif pathname == "/page-1":
return [
html.H1('Grad School',
style={'textAlign':'center'}),
dcc.Graph(id='bargraph',
figure=px.bar(df, barmode='group', x='Date',
y=['Girls Grade School', 'Boys Grade School']))
]
elif pathname == "/page-2":
return [
html.H1('High School',
style={'textAlign':'center'}),
dcc.Graph(id='bargraph',
figure=px.bar(df, barmode='group', x='Date',
y=['Girls High School', 'Boys High School']))
]
# If the user tries to reach a different page, return a 404 message
return dbc.Jumbotron(
[
html.H1("404: Not found", className="text-danger"),
html.Hr(),
html.P(f"The pathname {pathname} was not recognised..."),
]
)
# %%
#app.callback([Output("students", "figure")],
[Input("date-range", "start_date"),
Input("date-range", "end_date")])
def update_charts(start_date, end_date):
# read data upon refresh browser
df = pd.read_csv('iranian_students.csv')
# masks
mask1 = (
(df.Date >= pd.to_datetime(start_date))
& (df.Date <= pd.to_datetime(end_date))
)# daily sgym signups
# filtered dataframes
filtered_data1 = df.loc[mask1, :].reset_index(drop=True)
# plots
# daily sgym signups
trace = []
trace.append(go.Bar(
x = filtered_data1['Date'],
y = filtered_data1['Girls Kindergarten'],
name = 'Count of students'
))
# add moving average
filtered_data1['Moving Avg'] = filtered_data1['Girls Kindergarten'].rolling(window=7, min_periods=1).mean()
trace.append(go.Scatter(x=filtered_data1['Date'], y=filtered_data1['Moving Avg'], name = 'Rolling Mean=7'))
students_fig = {'data': trace,
'layout': go.Layout(
{"title": {"text": "Student Count\n"
'('+str(start_date)+' to '+str(end_date)+')',
"x": 0.05, "xanchor": "left"},
"xaxis": {"fixedrange": False, 'title':'Date',
'tickmode':'linear', 'automargin':True},
"yaxis": {"fixedrange": False, 'title':'Count of Signups'},
'xaxis_tickformat':'%d %b',
'title_font_size': 14,
'hovermode':'closest',
'legend_title_text':'Gym',
'hovermode':'closest',
},) }
return students_fig
# %%
if __name__=='__main__':
app.run_server(debug=True, port=3000)
The first error is telling you exactly where the problems start. The problem is in the file path that you define when updating the graph.
instead of this:
def update_charts(start_date, end_date):
# read data upon refresh browser
df = pd.read_csv('iranian_students.csv')
You need to do this:
def update_charts(start_date, end_date):
# read data upon refresh browser
df = pd.read_csv('Bootstrap\Side-Bar\iranian_students.csv')
the second problem is that you
call function serve_layoutOnReload without the brackets?
app.layout = serve_layoutOnReload
instead of
app.layout = serve_layoutOnReload()
The third problem is in how you define your layout. See this discussion for reference.This discussion from January is using the same code/database that you have and they are facing the same issues.
I would try defining the layout directly like this:
app.layout = html.Div([])

Python Dash - Dynamic and Static input options in same callback function

I need to write a call back function which will have a dynamic input component and a static input component. But putting them together is throwing error.
This is what I have done so far -
#app.callback(
[Output("new_list", "children")],
[
#Static Input Component
Input("clear", "n_clicks"),
#Dynamic Input Component
Input(str(i), "value") for i in item_list[['item3','item2','item1']].stack().groupby(level=0).agg(' '.join)
],
)
###########################################################
## Updated sample working code
import dash
import dash_html_components as html
import dash_bootstrap_components as dbc
from dash import callback_context
from dash.dependencies import Input, Output, State
import numpy as np
import pandas as pd
import dash_table as dt
item1 = ['A','A','B','B']
item2 = ["W","X","Y","Z"]
item3 = ["L",np.nan,'M','L']
item_list = pd.DataFrame(list(zip(item1,item2,item3)),columns=["item1","item2","item3"])
cat_list = item_list.item1.unique()
global itemlist
itemlist = pd.DataFrame( columns = ['item','qty'])
for item in item_list[['item3','item2','item1']].stack().groupby(level=0).agg(' '.join):
itemlist.loc[len(itemlist)] = (item,0)
def Add_item(data,data2):
print("BILLING: Add_item()")
item_list = dbc.Card(children = [
generate_item(data,data2),
dbc.Row([
dbc.Col([
dbc.Button(
html.H5("Cancel"), id="cancel", className="ml-auto", color = "danger"
)
]),
dbc.Col([
dbc.Button(
html.H5("Clear"), id="clear", className="ml-auto", color = "info"
)
]),
],style = {"width": "71rem"})
],
id="modal-body-newitem"
)
return item_list
def generate_item(item_list,itemlist):
buttons = html.Div(children =[
dbc.Row([
dbc.Button(
html.H5(str('+ ' + a)),
id= str(a),
className="mb-3",
color="primary",
block=True
),
dbc.Collapse([
dbc.Card([
dbc.Row([
dbc.Col([dbc.Button(html.H5(str('+ ' + b)),className="mb-3",color="info",block=True, disabled = True)],width = 10),
dbc.Col([dbc.Input(type="number", min=0, max=20, step=1,value = itemlist.loc[itemlist.item == b,'qty'],id = b )],width = 2)
]) for b in item_list.loc[item_list.item1== a, ('item3','item2','item1')].stack().groupby(level=0).agg(' '.join).sort_values()
#,dbc.Card(dbc.CardBody(str("This content is for " + a)))
],body = True, style = {"width": "71rem", "justify": "centre", "align": "centre"})
],id= str("collapse" + a))
]) for a in item_list.item1.unique()
])
return buttons
app = dash.Dash(__name__,external_stylesheets=[dbc.themes.CYBORG])
app.config.suppress_callback_exceptions = True
server = app.server
app.title="Test"
app.layout = html.Div([
dbc.Card(
dbc.CardBody(
[
html.H5("New Item", className="card-title"),
Add_item(item_list,itemlist),
html.Div(id = 'new_list')
]
)
)
])
#Item Ctegory List
#app.callback(
[Output(str("collapse" + i), "is_open") for i in cat_list],
[Input(str(i), "n_clicks") for i in cat_list]+
[Input("cancel", "n_clicks")])
def toggle_collapse_category_box(*args):
trigger = callback_context.triggered[0]
print("MYBIZZAPP:toggle_collapse_category_box: Call - "+str(callback_context.triggered))
if not callback_context.triggered or trigger["prop_id"].split(".")[0] == 'cancel':
print('MYBIZZAPP:toggle_collapse_category_box: Not Triggered/Cancel')
global isopn
isopn = [False] * len(cat_list)
else:
print('MYBIZZAPP:toggle_collapse_category_box: Triggered')
for i in range(len(cat_list)):
if cat_list[i] == trigger["prop_id"].split(".")[0]:
isopn[i] = not isopn[i]
return isopn
### Clear item list
#app.callback(
[Output(str(i), "value") for i in item_list[['item3','item2','item1']].stack().groupby(level=0).agg(' '.join)],
[Input("cancel", "n_clicks"),
Input("clear", "n_clicks")])
def clear_item_box(n2,n3):
trigger = callback_context.triggered[0]
print("MYBIZZAPP:clear_item_box: Call - "+str(callback_context.triggered))
if trigger["prop_id"].split(".")[0] == 'cancel':
print("MYBIZZAPP:clear_item_box: cancel order")
itemlist = pd.DataFrame( columns = ['item','qty'])
for item in item_list[['item3','item2','item1']].stack().groupby(level=0).agg(' '.join):
itemlist.loc[len(itemlist)] = (item,0)
elif trigger["prop_id"].split(".")[0] == 'clear':
print("MYBIZZAPP:clear_item_box: clear order")
return [0] * len(item_list)
#Menu item list
#app.callback(
[Output("new_list", "children")],
[Input(str(i), "value") for i in item_list[['item3','item2','item1']].stack().groupby(level=0).agg(' '.join)]+
[Input("clear", "n_clicks"),
Input("cancel", "n_clicks")])
def menu_item_list(*args):
trigger = callback_context.triggered[0]
print("MYBIZZAPP:menu_item_list: Call - "+str(callback_context.triggered))
print(trigger["prop_id"].split(".")[0])
if not callback_context.triggered :
print('MYBIZZAPP:menu_item_list: Not Triggered')
global itemlist
itemlist = pd.DataFrame( columns = ['item','qty'])
for item in item_list[['item3','item2','item1']].stack().groupby(level=0).agg(' '.join):
itemlist.loc[len(itemlist)] = (item,0)
else:
print('MYBIZZAPP:menu_item_list: Triggered')
itemlist.loc[itemlist.item == trigger["prop_id"].split(".")[0],'qty'] = trigger["value"]
if trigger["prop_id"].split(".")[0] == 'clear' or trigger["prop_id"].split(".")[0] == 'clear':
print('MYBIZZAPP:menu_item_list: Clear|Cancel')
itemlist['qty'] = 0
print('MYBIZZAPP:menu_item_list: Item List')
print(itemlist)
return ['Total items selected - ' + str(sum(itemlist.qty))]
if __name__ == '__main__':
app.run_server(debug=True)
If I do not mention the static input component, code works fine. But I need to have the static component as well.
Overall problem that I am trying to work on is - I have dynamic input text boxes, if and when a value is entered in any of these boxes, that value needs to be accounted for and returned as a table. Also, there is a 'clear' button. If clicked, all values should be re-set to default pre-defined value.
Thanks for your help in advance.
Thank you for updating with the error. This issue is coming from your list comprehension. When you add the first input, it creates invalid syntax for the comprehension. This should work, though:
#app.callback(
[Output("new_list", "children")],
[
#Static Input Component
Input("clear", "n_clicks"),
] + [
#Dynamic Input Component
Input(str(i), "value") for i in item_list[['item3','item2','item1']].stack().groupby(level=0).agg(' '.join)
],
)

Categories