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
Related
I am building a table that updates the values of an output DF into a csv file (or whatever output defined).
I defined a generate_agrid(df) function that outputs a class that contains a data method that is a pd.DataFrame. When I run the code grid_table = generate_agrid(df), the grid_table generated contains the original df, even if I modify it in the UI. I noticed that when I checked the input that my update function received.
What I want is to:
Graph the data in df -> update DF data in the UI and return -> save new df data into a csv every time I press update button
Why does my generate_agrid method always returns the initial DF used as an input? How can i update it?
My code
import streamlit as st
from metrics.get_metrics import get_data
from metrics.config import PATH_SAMPLES
filename: str = 'updated_sample.csv'
save_path = PATH_SAMPLES.joinpath(filename)
def generate_agrid(data: pd.DataFrame):
gb = GridOptionsBuilder.from_dataframe(data)
gb.configure_default_column(editable=True) # Make columns editable
gb.configure_pagination(paginationAutoPageSize=True) # Add pagination
gb.configure_side_bar() # Add a sidebar
gb.configure_selection('multiple', use_checkbox=True,
groupSelectsChildren="Group checkbox select children") # Enable multi-row selection
gridOptions = gb.build()
grid_response = AgGrid(
data,
gridOptions=gridOptions,
data_return_mode=DataReturnMode.AS_INPUT,
update_on='MANUAL', # <- Should it let me update before returning?
fit_columns_on_grid_load=False,
theme=AgGridTheme.STREAMLIT, # Add theme color to the table
enable_enterprise_modules=True,
height=350,
width='100%',
reload_data=True
)
data = grid_response['data']
selected = grid_response['selected_rows']
df = pd.DataFrame(selected) # Pass the selected rows to a new dataframe df
return grid_response
def update(grid_table: classmethod, filename: str = 'updated_sample.csv'):
save_path = PATH_SAMPLES.joinpath(filename)
grid_table_df = pd.DataFrame(grid_table['data'])
grid_table_df.to_csv(save_path, index=False)
# First data gather
df = get_data()
if __name__ == '__main__':
# Start graphing
grid_table = generate_agrid(df)
# Update
st.sidebar.button("Update", on_click=update, args=[grid_table])
Found the issue, it was just a small parameter that was activated.
While instantiating the AgGrid, I had to eliminate the reload_data=True parameter. Doing that, everything worked as expected and the data could be successfully updated after manually inputting and pressing "update"
This is how AgGrid must be instantiated
grid_response = AgGrid(
data,
gridOptions=gridOptions,
data_return_mode=DataReturnMode.AS_INPUT,
update_on='MANUAL',
fit_columns_on_grid_load=False,
theme=AgGridTheme.STREAMLIT, # Add theme color to the table
enable_enterprise_modules=True,
height=350,
width='100%',
)
I am building a web app where I get user input -> connect to api -> clean data and generate csv -> plot the csv using Dash.
I am able to update the graph without a problem, but I am not sure how to implement a download function for the dataframe that is used for the graph. Does it require 2 def () (one for the graph, other for the download button) functions after the #app.callback()? Or can this be done by wrapping the download function within the graph function?
Any pointers would be appreciated.
#app.callback(
Output('dash_graph', 'figure'),
[Input('button', 'n_clicks'),
State('ticker_input', 'value'),
State('start_date_input', 'date'),
State('end_date_input', 'date'),
State('entry_price_input', 'value'),
State('spread_input', 'value')
]
)
def update_result(n_clicks, ticker_input, start_date_input, end_date_input, entry_price_input, spread_input):
if n_clicks is None:
return dash.no_update
else:
API_TOKEN = "demo"
url = *some url*
response = urllib.request.urlopen(url)
eod_data = json.loads(response.read())
eod_data_df = pd.json_normalize(eod_data)
full_list = prep_df(eod_data_df)[0]
date_list = prep_df(eod_data_df)[1]
eod_data_df = prep_graph(start_date_input, end_date_input, full_list, date_list, entry_price_input, entry_price_input, spread_input)
df = eod_data_df.copy()
* todo: download this df here *
chart = px.bar(
data_frame=df,
x='date',
y='count',
title='Time Series Chart' + '<br>' + '<span style="font-size: 10px;">{} | {} to {} | ${:.2f} to ${:.2f} | Spread ${}</span>'.format(ticker_input, start_date_input, end_date_input, entry_price_input, entry_price_input+spread_input, spread_input),
# title=url
)
return (chart)
#-------------------------------------------------------------------------------
if __name__ == '__main__':
app.run_server(debug=True, dev_tools_ui=False)
Not sure how your app looks, I usually leave a button reserved for downloading data.
Do you want to have a separate button to do it? Or should it be triggered with the button that you already have ('button')?
In any case, you will need to add a dcc.Download(id="download-dataframe") to your layout, and then you callback should have an output Output(component_id="download-dataframe", component_property='data'), then your callback should create the csv file and return send_file('your_file.csv')
Whether you want it in the same button or a different one will mean if you need to create a new callback or not.
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?
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.
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