Inputs in Chained Call backs - dash - python

Is it possible to give inputs to a callback function which is inside another callback? In the below example I need to process each of the dict_item['Name'] which is getting passed from the outer call back function in a for loop.
# Outer Call back
#app.callback(
dash.dependencies.Output("dummydiv", "children"), # Dummy Output
[dash.dependencies.Input("interval1", "n_intervals")], # Interal Triger
[dash.dependencies.Input("table", "data")],
) # dcc.Store which stores values
def use_table(n, data):
print(type(data))
print("Outer Called")
if data:
for dict_item in data:
#app.callback(
dash.dependencies.Output(
"lable1", "children"
), # Output to print the values in a llop
dash.dependencies.Input(dict_item["Name"]),
)
def printing(s_name):
sn = s_name
print(sn)
return sn # Return "Name to Print" to id "Lable1"
return "" # Dummy String - Print Nothing to id "dummydiv"
return dash.no_update
Unfortunately, I am not able to pass the input parameter to the inner call back function. It returns the below error:
dash.dependencies.Input(dict_item['Name])
TypeError: init() missing 1 required positional argument: 'component_property'
How do I pass the variable from the outer call back to the inner call back? Or is there any other possible way to implement this logic?
Edit: Reproducible Code
import dash
import dash_core_components as dcc
import dash_html_components as html
import dash_table
from dash.dependencies import Output, Input, State
from datetime import datetime
user_key = "Name"
# Setup table.
columns = ["Name", "Age", "Place", "Vaccinated"]
table = dash_table.DataTable(
columns=[{"name": column, "id": column} for column in columns], data=[], id="table"
)
# Create app.
app = dash.Dash(prevent_initial_callbacks=True)
app.layout = html.Div(
[
html.Div(
[dcc.Input(id=column, value=column) for column in columns]
+ [html.Button("Save", id="save"), dcc.Store(id="cache", data=[]), table]
),
html.Div(
[
dcc.Interval(id="interval1", interval=5 * 1000, n_intervals=0),
html.H1(id="dummydiv", children=""),
html.H1(id="label1", children=""),
]
),
]
)
#app.callback(
Output("table", "data"),
[Input("save", "n_clicks")],
[State("table", "data")] + [State(column, "value") for column in columns],
)
def update_table(n_clicks, data, *args):
record = {columns[i]: arg for i, arg in enumerate(list(args))}
# If the record (identified by user_key) already exists, update it.
try:
record_index = [record[user_key] for record in data].index(record[user_key])
data[record_index] = record
# Otherwise, append it.
except ValueError:
data.append({columns[i]: arg for i, arg in enumerate(list(args))})
# Return the updated data.
return data
#app.callback(
dash.dependencies.Output("label1", "children"),
[dash.dependencies.Input("interval1", "n_intervals")],
[dash.dependencies.Input("table", "data")],
)
def use_table(n, data):
print(type(data))
if data:
for dict_item in data:
print(dict_item["Name"])
# for key in dict_item:
# print (dict_item[key])
return dict_item["Name"]
if __name__ == "__main__":
app.run_server()
In brief, The above app allows the user to enter certain values and displays a table below and stores the data entered. I need to display the values(One name at a time, not as a list) retrived in loop from the column Name to the Output component with id lable1. Since adding a return statement in the callback function force exits the ```for loop``, I used a nested callback logic.

A Dash Input requires a value to be passed to the component_id and component_property parameters.
As the error is telling you, you're only passing one:
missing 1 required positional argument: 'component_property'
Compare the syntax of your inner callback to your outer callback.
Having said all this, don't nest callbacks in the first place. Define callbacks upfront.
Update based on edit
I need to display the values(One name at a time, not as a list) retrived in loop from the column Name to the Output component with id lable1. Since adding a return statement in the callback function force exits the for loop.
You don't need to immediately return something in the loop. You can first build up your list of values and return afterwards. You could initialize a list and append elements to that list when iterating or you could use a list comprehension and do something like this:
#app.callback(
dash.dependencies.Output("label1", "children"),
[dash.dependencies.Input("interval1", "n_intervals")],
[dash.dependencies.Input("table", "data")],
)
def use_table(n, data):
return ", ".join([dict_item["Name"] for dict_item in data])
This displays all names for every row separated by a comma and a space using a list comprehension, no nested callback needed. Adjust depending on what you want to display.

Related

Use a button to set values in drop down using dash

I was wondering is it possible to set multiple values to a drop down value using a button. What I want to be able to do is filter the drop down and then if the user presses the submit button, all options that contain the search_value are added as the drop down value. For example in my code, if you type in 'a' into the drop down search bar then 'Montreal' and 'San Francisco' appear. If the user were to then press the submit button both these options should be added as values to the drop down at once. At the moment I am able to save the most recent search value to user put in but I am having trouble dynamically altering the drop down value. Any help would be greatly appreciated!! Here is my code: FYI: I am running this in a Colab Notebook
**Correct answer
from jupyter_dash import JupyterDash # pip install dash
import dash_html_components as html
import dash_core_components as dcc
from dash.dependencies import Output, Input, State
import pandas as pd # pip install pandas
import plotly.express as px
import dash_table
import math
from dash import no_update
external_stylesheets = ['https://codepen.io/chriddyp/pen/bWLwgP.css']
app = JupyterDash(__name__, external_stylesheets=external_stylesheets)
options = [
{"label": "New York City", "value": "NYC"},
{"label": "Montreal", "value": "MTL"},
{"label": "San Francisco", "value": "SF"},
]
app.layout = html.Div([
html.Div(id='output_data'),
html.Div(id="hidden-search-value", style={"visibility":"hidden"}),
html.Div(id="value-test", style={'color':'white'}),
html.Div(html.Button('Submit', id = 'button', n_clicks=0), style={'width':'49%', 'display':'inline-block'}),
html.Div(
dcc.Dropdown(id="my-multi-dynamic-dropdown", multi=True, clearable = True, options = options, style={'width':'49%', 'display':'inline-block'})),
])
#app.callback(
[Output("hidden-search-value", "children"),
Output("value-test", "children")],
Input(component_id="my-multi-dynamic-dropdown", component_property="search_value"),
)
# save previous search value in hidden variable
def update_hidden_value(value):
if value == "":
raise PreventUpdate
return value, "Select all values containing '{}'".format(value)
#app.callback(
Output("my-multi-dynamic-dropdown", "value"),
Input(component_id="button", component_property="n_clicks"),
[
State(component_id="output_data", component_property="children"),
State(component_id="hidden-search-value", component_property="children")
]
)
# submit button logic: use saved search value to update drop_down value
def update_multi_options(n_clicks, children, search_value):
# submit button pressed
if int(n_clicks) > 0:
values = []
for o in options:
# all values containing search value
if search_value.lower() in o["label"].lower():
values.append(o["value"])
else:
continue
return values
else:
return no_update
app.run_server(mode='inline', port=8030)
I believe you have the wrong ordering of your input arguments to your callback update_multi_options. It should be:
#app.callback(
Output("my-multi-dynamic-dropdown", "value"),
Input(component_id="button", component_property="n_clicks"),
State(component_id="output_data", component_property="children")
)
def update_multi_options(n_clicks, children):
Now, regarding the dynamic updating using the search feature, it turns out that whenever you click out of the search bar, the search_value is cleared. So I suggest perhaps tracking the search_value in a temporary hidden variable, if this is a low risk option for your application. An approach to do this would be the following:
app.layout = html.Div([
html.Div(id='output_data'),
html.Div(html.Button('Submit', id = 'button', n_clicks=0)),
html.Div(id="hidden-search-value", style={"visibility":"hidden"}),
html.Div([
"Multi dynamic Dropdown",
dcc.Dropdown(id="my-multi-dynamic-dropdown", multi=True, clearable = True, options = options),
]),
])
.
.
.
#app.callback(
Output("my-multi-dynamic-dropdown", "value"),
[
Input(component_id="button", component_property="n_clicks"),
],
[
State(component_id="output_data", component_property="children"),
State(component_id="hidden-search-value", component_property="children")
]
)
def update_multi_options(n_clicks, children, search_value):
# Make sure that the set values are in the option list, else they will disappear
# from the shown select list, but still part of the `value`.
if int(n_clicks) > 0:
values = []
for o in options:
if search_value in o["label"]:
values.append(o)
else:
continue
return values
else:
return no_update
#app.callback(
Output("hidden-search-value", "children"),
Input(component_id="my-multi-dynamic-dropdown", component_property="search_value"),
)
def update_hidden_value(value):
print(value, type(value))
if value == "":
raise PreventUpdate
return value
The only limitation with this approach would be that when you type something into the search and click submit, then you manually erase the search value and re-click on submit, it'll be stuck on the last typed in value. But, I don't see this being an issue, since you can overwrite this with another callback if this is undesirable.
In summary, you'll need to track the value you are typing in (eg. dcc.Dropdown.search_value) then trigger the filtering.

Plotly Dash : Passing data from a callback to another callback with dcc.Store component

I have a multi-tab / page application and I'd like to pass the data (a dictionary) returned by a function call in one callback to another callback. Here's some code:
import dash
from dash import dcc
import dash_bootstrap_components as dbc
#app layout
layout = html.Div([
dcc.input(id="comp"),
dcc.Store(id="id1")
html.Div(id='dummy-div')
])
# callbacks
app.callback1([
output('comp','value'),
output('dummy-div','value'),
],
[
input('in1','value'),
input('in2','value')
],
):
def update(in1, in2):
# function call
res = func(in1, in2)
# for reproducibility
res = {'name':'xyz', 'addr':'123'}
return (in2, res)
app.callback(output('id1', 'data'),
[
input('comp','value'),
input('dummy-div','value)
],
):
def store(store, dummy):
# save to store component
# for reproducibility
res = {'name':'xyz', 'addr':'123'}
return {'res': res,
'comp': 12
}
I get an dash exception when I run the above code:
raise exceptions.InvalidCallbackReturnValue(
dash.exceptions.InvalidCallbackReturnValue: The callback for `[<Output `dummy-div.value`>]`
returned a value having type `list`
which is not JSON serializable.
The value in question is either the only value returned,
or is in the top level of the returned list,
and has string representation
`[{'Name':'xyz','Address':'123'........}]`
In general, Dash properties can only be
dash components, strings, dictionaries, numbers, None,
or lists of those.
Basically, I'd like to save res which is dictionary returned by the function invoked to dcc.Store component. I'd need to have two separate callbacks because of the way app is structured. How do I pass res to the callback that saves to dcc.Store component?

Return ‘href’ value on a click event in Python Dash

I have been trying to return the ‘href’ value from click event in python-Dash application. Below is my code snippet:
import dash
import dash_html_components as html
from dash.dependencies import Input, Output, State
app = dash.Dash(
__name__,
meta_tags=[{"name": "viewport", "content": "width=device-width, initial-scale=1"}],
)
def make_table(df, val):
table = []
for index, row in df.iterrows():
rows = []
html.Td([
html.Div([row["col1"]]),
html.A(id = 'link',href=row["file-link"], children=row["link-name"], target="_blank"),
])
table.append(html.Tr(rows))
return table
app.layout = html.Div([
html.Table(
id="table-element",
className="table__container",
)
],
className="six columns",
),
#app.callback(
Output("link", 'pathname'),
[Input('link', 'n_clicks')],
[State('link', 'href')]
)
def open_link(n_clicks, href):
enable_open_link(href) #enable_open_link function takes in the href string value (a local filesystem link) and opens it up in a new window.
#app.callback(
Output("table-element", 'children'),
[Input("input-1-submit", 'n_submit')],
[State('input-1-submit', 'value')]
)
def update_output(ns1,val):
table = make_table(df,val)
return table
This code works upto some extent i.e. it does returns a href value, but not the one I click. It always returns the last href value stored inside the html table.
Is there a way to fetch the href value when I click on the link?
I know I can use Jquery to fetch the correct href value… but I didn’t find a way to integrate javascript within the call-back function.
I think it's because all the links you'r creating have the same id ='link'.
You'll need to find a way around that. One possibilty would be to generate the id when creating it based on the index of your df, but then you'll also have to create the corresponding callbacks. This thread tells you how it could be done. plotly dash: create multiple callbacks (with loop?)
Taking a clue from thread provided by #Rudertier, I was able to get the solution.
Below is the updated code snippet:
def make_table(df, val):
table = []
for index, row in df.iterrows():
rows = []
html.Td([
html.Div([row["col1"]]),
html.A(id = 'link'+str(index),href=row["file-link"], children=row["link-name"], target="_blank"),
])
table.append(html.Tr(rows))
return table
app.layout = html.Div([
html.Table(
id="table-element",
className="table__container",
)
],
className="six columns",
),
links = ['link1','link2','link3','link4','link5','link6','link7','link8','link9','link10']
for link in links:
#app.callback(
Output('{}'.format(link), 'pathname'),
[Input('{}'.format(link), 'n_clicks')],
[State('{}'.format(link), 'href')]
)
def open_link(n_clicks, href):
enable_open_link(href) #enable_open_link function takes in the href string value (a local filesystem link) and opens it up in a new window.
#app.callback(
Output("table-element", 'children'),
[Input("input-1-submit", 'n_submit')],
[State('input-1-submit', 'value')]
)
def update_output(ns1,val):
table = make_table(df,val)
return table

Dash App call back append new entry in data frame and replace if matches with any previous entry

I am trying to build a dash app which takes input from users and append each entry in the data frame, but it's replacing the old entry and generating a new one. I am not sure what's wrong in the callback.
also trying to build a logic if the user entry matches with any row of data frame then only it will replace else append.
your help will be much appreaciated:
#app.callback(Output('save-query', 'children'),
[Input('save', 'n_clicks')],
[State('ad_account_id', 'value'),
State('app_id', 'value'),
State('access_token', 'value'),
State('app_secret', 'value'),
State('metrics', 'value'),
State('breakdown', 'value'),
State('start-date', 'date'),
State('end-date', 'date'),
State('save-as', 'value')
],
)
def save_query(clicks, ad_account_id, app_id, access_token, app_secret, metrics, breakdown,
start_date, end_date, save):
if clicks is not None:
my_ad_account = ad_account_id
my_app_id = app_id
my_access_token = access_token
my_app_secret = app_secret
my_metrics = metrics
my_breakdown = breakdown
my_start_date = start_date
my_end_date = end_date
my_save = str.lower(save)
data = [[my_save, my_ad_account, my_app_id, my_access_token, my_app_secret, my_metrics, my_breakdown,
my_start_date,
my_end_date]]
df = pd.DataFrame(data, columns=['report_name', 'ad_account', 'app_id', 'access_token',
'app_secret', 'metrics', 'breakdown',
'start_date', 'end_date'])
dff = df.append(df)
return html.Div([
dash_table.DataTable(
css=[{'selector': '.row',
'rule': 'margin: 0; white-space: inherit; overflow: inherit; text-overflow: inherit;'}],
id='table',
columns=[{"name": i, "id": i} for i in dff.columns],
data=dff.to_dict("rows"), )],
style={'margin-top': 30, 'display': 'inline-block', 'margin-left': 20, 'width': '100%'})
Since mutation of global variables is discouraged in Dash, the standard approach to enable access the to current data would be to add a component that holds the data as a State argument. It could be a separate component (such as a Store component) or simply the data table itself. Here is a small example demonstrating the latter approach,
import dash
import dash_core_components as dcc
import dash_html_components as html
import dash_table
from dash.dependencies import Output, Input, State
user_key = 'access_token'
# Setup table.
columns = ['report_name', 'ad_account', 'app_id', 'access_token']
table = dash_table.DataTable(columns=[{"name": column, "id": column} for column in columns], data=[], id="table")
# Create app.
app = dash.Dash(prevent_initial_callbacks=True)
app.layout = html.Div([dcc.Input(id=column, value=column) for column in columns] +
[html.Button("Save", id="save"), dcc.Store(id="cache", data=[]), table])
#app.callback(Output("table", "data"), [Input("save", "n_clicks")], [State("table", "data")] +
[State(column, "value") for column in columns])
def append(n_clicks, data, *args):
record = {columns[i]: arg for i, arg in enumerate(list(args))}
# If the record (identified by user_key) already exists, update it.
try:
record_index = [record[user_key] for record in data].index(record[user_key])
data[record_index] = record
# Otherwise, append it.
except ValueError:
data.append({columns[i]: arg for i, arg in enumerate(list(args))})
# Return the updated data.
return data
if __name__ == '__main__':
app.run_server()
As a sidenote, the prevent_initial_callbacks keyword is new as per Dash 1.12.0. As indicated by the name, it prevents the initial callback, thereby eliminating the need for the if clicks is not None: check.

Flask Dash passing a variable generated in a callback to another callback

In a callback, a dataframe is created from user inputs. I need to use that dataframe in another function, in order to serve it to the user.
I read that server.route can do this, with Flask SendFile, but I can't access the dataframe since I cannot use global variables.
I have read there is a hidden div method but I don't know how I can access a html div property from inside of python.
'''
server = flask.Flask('app')
app = dash.Dash(__name__,
external_stylesheets=external_css,
server=server)
master = pd.read_csv('master_dataframe.csv')
#server.route("/downloadable/")
def download_file():
df = # The dataframe I need that is in the other function
buffer = io.BytesIO()
dff.to_excel(buffer) # write to BytesIO buffer
buffer.seek(0)
return send_file(
buffer,
attachment_filename='data.xlsx',
as_attachment=True,
cache_timeout=0
)
#app.callback(
Output('plot_button','n_clicks_timestamp'),
[Input('account_selector','value')]
)
def generate_layout(value):
df = make_something(master, value)
return html_layout
'''
You could output the contents of the dataframe in JSON format to the children prop of a div with display='none'. Then use another callback with the children of that div as its Input, and you'll be able to read the JSON and use that data.
Quick example:
#app.callback(
Output('my-hidden-div','children'),
[Input('my-input','value')] # whatever this will be
)
def generate_df_callback(value):
df = make_df_from_input(value)
return df
#app.callback(
Output('my-output', 'value'), # whatever this will be
[Input('my-hidden-div', 'children')]
def use_df_callback(df):
foo = do_something_with_df(df)
return foo

Categories