I wrote a Dash app whose source code is shared below:
import dash
from dash 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
import time
app = Dash(__name__, external_stylesheets=[dbc.themes.BOOTSTRAP], url_base_pathname='/self_serve/')
server = app.server
reset_flag = False
counter = 0
app.title = 'jammed dash app'
app.layout = html.Div([
# buttons
dcc.Input(id='main',placeholder='Main Value'),
dcc.Input(id='filter1',placeholder='filter 1'),
dcc.Input(id='filter2',placeholder='filter 2'),
dcc.Input(id='filter3',placeholder='filter 3'),
dcc.Input(id='filter4',placeholder='filter 4'),
html.Div(id='output',children='')
])
#metric list
#app.callback(
Output(component_id='filter1',component_property='value'),
Output(component_id='filter2',component_property='value'),
Output(component_id='filter3',component_property='value'),
Output(component_id='filter4',component_property='value'),
[
Input(component_id='main',component_property='value')
]
)
def update_filter(main):
# clear up all filters if main is provided
global reset_flag
reset_flag = True
return '','','',''
#app.callback(
Output(component_id='output',component_property='children'),
[
Input(component_id='main',component_property='value'),
Input(component_id='filter1',component_property='value'),
Input(component_id='filter2',component_property='value'),
Input(component_id='filter3',component_property='value'),
Input(component_id='filter4',component_property='value'),
]
)
def calculate(*args):
# do some intensive calculation based on the inputs, but I do not want the clearing action to trigger this callback undesirably
ctx = dash.callback_context
print('\n')
print('************ inside calculate *************')
print('triggered:', ctx.triggered)
print('inputs:', ctx.inputs)
# my idea for solving this problem
global reset_flag, counter
if reset_flag:
counter += 1
if counter <= 4:
print('counter:',counter)
print('reset_flag:',reset_flag)
return ''
else:
reset_flag = False
counter = 0
print('we passed into the correct flow!')
pass
# below is some intensive calculation using pandas.read_sql(), substituted by time.sleep()
print('Wait 10 seconds here')
time.sleep(10)
output = ''
for item in args:
if item:
output += item
print('output:',output)
return output
if __name__ == '__main__':
app.run_server(debug=True)
I need to perform some intensive calculations (e.g. sql code) in the 2nd callback ("calculate"). I would also like to clear up all filter elements whenever value in "main" changes, which is implemented in the 1st callback ("update_filter").
The issue is that, when each filter is cleared in the 1st callback, dash is making call to the 2nd callback in quick succession and the program is jammed.
My question is: how to avoid firing the 2nd callback when the 1st callback is called?
My idea is to track the number of times the 2nd callback is called "redundantly" and letting the "correct" call to pass while returning empty output for "redundant" calls. I tried to implement using a global counter, but this is to no avail.
And in general, what is the best/common practice for avoiding these undesirable, chained callback firing?
Thanks a lot in advance!
Here is my rather crude implementation of the lock on your calculate function using a file. Global variables, I think, are problematic given various threading issues etc. The idea is when calculate does its heavy calculations it puts a '1' into a file, and when it is done it puts '0' there. If in the meantime the function is called again, it checks the lockfile and if there is '1' there it exists without triggering recalculation.
import dash
from dash 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
import time
app = Dash(__name__, external_stylesheets=[
dbc.themes.BOOTSTRAP], url_base_pathname='/self_serve/')
server = app.server
reset_flag = False
counter = 0
lockfilename = 'lockfile.txt' # this is where we keep the lock
with open(lockfilename, 'w') as fw:
fw.write('0') # unlock when we start
app.title = 'jammed dash app'
app.layout = html.Div([
# buttons
dcc.Input(id='main', placeholder='Main Value'),
dcc.Input(id='filter1', placeholder='filter 1'),
dcc.Input(id='filter2', placeholder='filter 2'),
dcc.Input(id='filter3', placeholder='filter 3'),
dcc.Input(id='filter4', placeholder='filter 4'),
html.Div(id='output', children='')
])
# metric list
#app.callback(
Output(component_id='filter1', component_property='value'),
Output(component_id='filter2', component_property='value'),
Output(component_id='filter3', component_property='value'),
Output(component_id='filter4', component_property='value'),
[
Input(component_id='main', component_property='value')
]
)
def update_filter(main):
# clear up all filters if main is provided
global reset_flag
reset_flag = True
return '', '', '', ''
#app.callback(
Output(component_id='output', component_property='children'),
[
Input(component_id='main', component_property='value'),
Input(component_id='filter1', component_property='value'),
Input(component_id='filter2', component_property='value'),
Input(component_id='filter3', component_property='value'),
Input(component_id='filter4', component_property='value'),
]
)
def calculate(*args):
# do some intensive calculation based on the inputs, but I do not want the clearing action to trigger this callback undesirably
ctx = dash.callback_context
print('\n')
print('************ inside calculate *************')
print('triggered:', ctx.triggered)
print('inputs:', ctx.inputs)
with open(lockfilename, 'r') as fr:
line = next(fr)
if(line[0] == '1'):
print('Calc is locked, early exit')
return dash.no_update
# below is some intensive calculation using pandas.read_sql(), substituted by time.sleep()
print('Starting heavy calc, locking the file')
with open(lockfilename, 'w') as fw:
fw.write('1')
print('Wait 10 seconds here')
for n in range(10):
print('.', end='')
time.sleep(1)
output = ''
for item in args:
if item:
output += item
print('output:', output)
print('Done with heavy calc, unlocking the file')
with open(lockfilename, 'w') as fw:
fw.write('0')
return output
if __name__ == '__main__':
app.run_server(debug=True)
In your case, what you might want to do is:
Input(main) -> Output(filter) # reset filter
# when filter changes, take main's state and use it to update output
Input(filter), State(main) -> Output(output)
This doesn't block because it is a chained callback: changing main clears filter, and changing filter triggers the second callback which takes main's state and updates output.
For time-intensive operations, you might want to cache those results. There is an entry in the doc showing this: https://dash.plotly.com/performance
Related
I am trying to store an edited Python Dash DataTable into a dcc.store container with a button component and use the saved data at a later point. However, once I change the original DataTable, the stored container also changes. I found the following post (save a modified dash datatable to dataframe) but was not able to figure it out.
My core question is how can I store (copy) some data in a Python dash app and then manipulate the original data without altering the stored copy.
# Libraries
import pandas as pd
import dash
from dash import html, dash_table
import dash_core_components as dcc
from dash.dependencies import Input, Output
# Data
test_data = pd.DataFrame([[1,2],[3,4]], columns=['Col1', 'Col2'])
saved_test_data = None
# App
app = dash.Dash(__name__)
app.layout = html.Div(children=[dash_table.DataTable(columns = [{"name": i, "id": i} for i in test_data.columns],
data = test_data.to_dict('records'),
id = 'test_data_table',
editable=True,
),
html.Button('Save data', id='save_test_data', n_clicks=0),
dcc.Store(id = 'test_data_store'),
dash_table.DataTable(id = 'check_table', data=saved_test_data),
],
style={'width': '50%', 'display': 'inline-block', 'padding-left':'25%', 'padding-right':'25%'}
)
# Callbacks
#app.callback(Output('test_data_store', 'data'),
[Input('save_test_data', 'n_clicks'), Input('test_data_table', 'data')])
def save_test_data(n, data):
if n == 0:
saved_test_data = None
else:
saved_test_data = data
return saved_test_data
#app.callback(Output('check_table', 'data'),
Input('test_data_store', 'data'))
def restore_saved_test_data(data):
return data
if __name__ == '__main__':
app.run_server(debug=True, use_reloader=False)
If I change a value in the upper table after pressing the button, the lower table also changes, but it should display the value saved before the click, i.e.
Original Table
Press the button to save the table.
Stored table should be displayed below
Changing the upper table should not change the lower table before hitting the button again, but it changes immediately.
There are two problems. First, you have the original table as an Input, which triggers the callback every time it changes. This should be a State instead,
State('test_data_table', 'data')
Second, and maybe not so important if you make the above change, is this condition, if n == 0:. This will always be False after one click of the button. From there, every time the table updates.
The logic behind the dash core component dropdown and the dash bootstrap components is a bit different and I would like to have the best of both worlds: the nice style from dbc and the functionality from dcc. However, modifying the css for the dcc to make it look nicer is complicated and I could not find an existing solution. To set up the dbc component requires some set up effort as each element in the drop down has an own id. Also if you want to directly get from the dropdown what is the selected value (if you ask the dropdown what are you actually showing) you cannot do that directly.
I thus wanted to setup a function that sets the drop down and its callback up automatically but I run into the problem that the callback is a nested function and is therefore not available globally. How can I change that? Or is there another way to build it.
What I want in the end is an easy way to set up the dbc dropdown such that it shows the selected value.
This is what I have so far (the not working solution):
import dash
import dash_bootstrap_components as dbc
import dash_html_components as html
from dash.dependencies import Input, Output
def selectable_dropdown(item_id="dropdown",
options=['option_1','option_2']):
# create the items and the ids for each item
dropdown_items = [dbc.DropdownMenuItem(item, id=item_id+'_'+item)
for item in options]
# create the dropdown menu
dropdown = dbc.DropdownMenu(
dropdown_items,
label="none",
addon_type="prepend",
bs_size="sm",
id=item_id
)
output = Output(item_id, "label")
inputs = [Input(item_id+'_'+item, "n_clicks") for item in options]
#app.callback(output,inputs)
def update_label(*args):
# get the triggered item
ctx = dash.callback_context
triggered_id = ctx.triggered[0]["prop_id"].split(".")[0]
# get the label for the triggered id or return no selection
if (np.array([n==None for n in args]).all()) or not ctx.triggered:
return "no selection"
else:
return [label for label in options if item_id+'_'+label == triggered_id]
return dropdown
app = dash.Dash(
external_stylesheets=[dbc.themes.BOOTSTRAP]
)
app.config['suppress_callback_exceptions'] = True
app.layout = \
html.Div([selectable_dropdown(item_id="target_select",
options=["option1 ", "option 2", "option3"])])
if __name__ == "__main__":
app.run_server(debug=False, host = '0.0.0.0', port = 1234)
thats how it should look like (a working example) but I wnat it in a more generalized way and in the best way just in one function or class:
import dash
import numpy as np
import dash_bootstrap_components as dbc
import dash_html_components as html
from dash.dependencies import Input, Output
options=["option1 ", "option 2", "option3"]
item_id = 'dropdown'
dropdown_items = [dbc.DropdownMenuItem(item, id=item_id+'_'+item)
for item in options]
# create the dropdown menu
dropdown = dbc.DropdownMenu(
dropdown_items,
label="none",
addon_type="prepend",
bs_size="sm",
id=item_id)
output = Output(item_id, "label")
inputs = [Input(item_id+'_'+item, "n_clicks") for item in options]
app = dash.Dash(
external_stylesheets=[dbc.themes.BOOTSTRAP,'./assets/stylesheet.css']
)
app.config['suppress_callback_exceptions'] = True
app.layout = \
html.Div([dropdown])
#app.callback(output,inputs)
def update_label(*args):
# get the triggered item
ctx = dash.callback_context
triggered_id = ctx.triggered[0]["prop_id"].split(".")[0]
# get the label for the triggered id or return no selection
if (np.array([n==None for n in args]).all()) or not ctx.triggered:
return "no selection"
else:
return [label for label in options if item_id+'_'+label == triggered_id]
if __name__ == "__main__":
app.run_server(debug=False, host = '0.0.0.0', port = 1234)
I am working on a project on which I have to monitor a ftp directory for any new files which appear , once a new file appears I want to extract data from and plot the results in real time so far I have sorted out the ftp function that monitors the directory , for testing purpose I am using my phone as a test ftp server and I have created a function which uploads a new file every n seconds :
from ftplib import FTP
from time import sleep
import os
ftp = FTP()
ftp.connect('192.168.1.109', 2221)
ftp.login('android', 'android')
ftp.cwd('/test_folder')
folder = 'C:\\Users\\QC\\Desktop\\sample_files_to_upload'
for file in os.listdir(folder):
fp = open(folder + file,'rb')
ftp.storbinary('STOR %s' % os.path.basename(file), fp, 1024)
sleep(2)
fp.close()
sleep(2)
print("File {} uploaded sucessfully".format(file))
I also have a function which watches the directory for any new files which appear in the directory and as soon as the file appears it copies it on the local machine performs data extraction prints the output and removes the file:
from ftplib import FTP
from time import sleep
import time
import os
def monitor_ftp():
ftp = FTP()
ftp.connect('192.168.1.109', 2221', 21)
ftp.login('android', 'android)
ftp.cwd('/test_folder')
print("Connection Established {}".format(ftp.getwelcome()))
direct = 'C:\\Users\\QC\\Desktop\\local_temp_directory\\'
old_files = ['1']
#print(old_files)
while True:
try:
new_files = ftp.nlst()
#print(new_files)
#print(new_files)
if len(old_files) != 0 and new_files != old_files:
changes = [i for i in new_files if i not in old_files]
#
# print(changes)
for x in changes:
filename = str(direct + x)
localfile = open(filename, 'wb')
ftp.retrbinary('RETR'+' ' + x , localfile.write, 1024)
localfile.close()
xcorr = extract_function(filename)
print("updating data ***************************************************")
print("found new file---> {}".format(str(filename).split('\\')[-1]))
print("Calculating cross-correlation")
print("*****************************************************************")
print(" ")
sleep(3)
os.remove(filename)
a = time.perf_counter()
if time.perf_counter() > a + 20:
print("Done Waiting")
break
old_files = new_files
except KeyboardInterrupt:
ftp.quit()
I have also created a basic test_dash app :
import dash
import plotly.graph_objects as go
import dash_core_components as dcc
import dash_html_components as html
from dash.dependencies import Input, Output, State
import dash_bootstrap_components as dbc
from app import app
app = dash.Dash(__name__)
app.layout = html.Div(
[
dcc.Graph(id = 'live-graph', animate = True),
dcc.Interval(
id = 'graph-update',
interval = 10000,
n_intervals = 0
),
html.Div(dbc.Row([
dbc.Col(html.H2(id='check_update_div', children='check ' ))
]) )
]
)
#app.callback(
Output('check_update_div', 'children'),
[ Input('graph-update', 'n_intervals') ]
)
def update_graph_scatter(n):
print('one loop done ')
a = monitor_ftp()
return ("this is X {}".format(a))
I want to tie everything together and create a dash app which monitors the ftp directory and plots the result of data extract , currently I don't have a graph in my app, I have just put a div which I want to update with the file name for every new file that appears in the folder , but I am not sure how to combine these functions together. Can you please help.
Edit:
I have got a working example which shows that ftp inside the callback function prevents the function from returning anything , if ftp is outside the return function then callback returns and updates the div every n_intervals
import dash
from dash.dependencies import Output, Input
import dash_core_components as dcc
import dash_html_components as html
import plotly
import random
import plotly.graph_objs as go
from collections import deque
from ftplib import FTP
from time import sleep
#ftp otside the function and function works fine
#Deactivae this part and activate ftp inside the function , and function
#stops returning anything and div is not updated every n_interval
ftp = FTP()
ftp.connect('10.199.44.240', 21)
ftp.login('display')
ftp.cwd('/home/display/test_qc')
print("Connection Established {}".format(ftp.getwelcome()))
direct = 'C:\\Users\\QC\\Desktop\\Gunlink_local\\'
app = dash.Dash(__name__)
app.layout = html.Div([
html.Div([
dcc.Interval(
id = 'graph-update',
interval = 10000,
n_intervals = 0
)
]),
html.Div([
html.H1(id='print_value', children="Output Value"),
])
])
#app.callback(
Output('print_value', 'children'),
[ Input('graph-update', 'n_intervals') ]
)
def update_value(n):
# ftp inside the function prevents the function from returning anything
# Div is not updated every n_interval
'''
ftp = FTP()
ftp.connect('10.199.44.240', 21)
ftp.login('display')
ftp.cwd('/home/display/test_qc')
print("Connection Established {}".format(ftp.getwelcome()))
a = ftp.nlst()
'''
a = ftp.nlst()
sleep(2)
print('one loop done ')
return ("this is X {}".format(a))
if __name__ == '__main__':
app.run_server()
You need the interval component.
Dash cannot do constant updates, like running that while loop nonstop, but you can have the interval set to fire a callback every X seconds. Using that callback, you can check the FTP server on that schedule for updates.
Check out this page of the docs for some good examples of using it to create a live-updating page.
In dash, how do I update the values of one dropdown into another checklist or slider?
In the below code, I am selecting one value from a dropdown which should update checklist values based on the selected value from the dropdown. Here I am partially successful in taking value from the dropdown but it's accumulating with older selected values in the checklist.
Please find below part of the code.
import dash
from dash.dependencies import Input, Output, State
import dash_core_components as dcc
import dash_html_components as html
import dash_table
import pandas as pd
from dash.exceptions import PreventUpdate
df = pd.read_csv('https://raw.githubusercontent.com/plotly/datasets/master/solar.csv')
external_stylesheets = ['https://codepen.io/chriddyp/pen/bWLwgP.css']
app = dash.Dash(__name__, external_stylesheets=external_stylesheets)
app.config['suppress_callback_exceptions'] = True
app.layout = html.Div([
dash_table.DataTable(
id='datatable-upload-container',
columns=[{"name": i, "id": i} for i in df.columns],
data=df.to_dict('records'),
),
html.Div(dcc.Dropdown(
id='data_selector1',
options=[
{'label': '', 'value': ''}
],
value=[]
)
),
html.Br(),
html.Div([
html.Div(id='numerical-slider'),
# this is a hack: include a hidden dcc component so that
# dash registers and serve's this component's JS and CSS
# libraries
dcc.Input(style={'display': 'none'})
])
])
#app.callback(Output('data_selector1', 'options'),
[Input('datatable-upload-container', 'data')])
def update_dropdown(rows):
print('updating menus')
numerical_col = [i for i in df.columns if df[i].dtypes != "object"]
col_labels=[{'label' :k, 'value' :k} for k in numerical_col]
return col_labels
#app.callback(Output('numerical-slider','children'),
[Input('data_selector1', 'value'),
Input('datatable-upload-container', 'data')])
def explanatory_cat_slider(value, rows):
if value:
categories, intervals = pd.cut(df[value], 3, retbins=True)
return html.Div(html.Label([value,
dcc.RangeSlider(id='numerical-slider',
min=min(intervals),
max=max(intervals),
step=None,
marks={str(val): str(round(val,2)) for val in intervals},
value = [intervals[0],intervals[-1]]
)
],style={'width': '50%', 'display': 'inline-block', 'textAlign': 'left'})
)
else:
return []
if __name__ == '__main__':
app.run_server(debug=False)
updated code...
I am getting an issue with explanatory_cat_slider, it's not getting updated with new selected values.
In the first image I can select one value of dropdown which automatically shows slider of that value
In the second image sliders getting accumulated on upon other. How do I rectify this issue?
In the last image, how it becomes slider overlapped
The code you posted is incomplete, because you have the Input ID data_selector and the ID datatable-upload-container in your callback, but they do not exist in your layout. I can see that the callback will have a problem with its Output as well, because it's trying to update the children of categorical-checklist with an element that contains an ID of the same. This would result in non-unique IDs in your layout, which will break.
I may be able to help more with the complete code but, essentially, you have the right idea to check if value: and run your code inside that block. Make sure you do not let the callback silently end, because that will return None and cause trouble for you. You should include a return [] or return '' at the end, or inside an else: block to protect against that, and keep the children component valid, even if only with an empty value.
You should also include the dcc.Checklist in your initial layout. Give it options=[] to start with and have your callback update its options prop. That should work but, if there are still problems, update your posted code and I'll take another look.
I would like to create a stopwatch using python plotly dash and the following is my code. Here I use the element dcc.Interval to update timing information every one second, but unfortunately, I found that it is not accurate when I used my iPhone built-in stopwatch to test its accuracy. Any suggestion on how how to correct my code? Thanks :)
# import modules
import dash
import dash_html_components as html
import dash_core_components as dcc
from dash.dependencies import Input, Output
# define functions
def sec_transform(i):
m, s = divmod(i, 60)
msg = '%02i:%02i' % (m, s)
return msg
# dashboard layout
app = dash.Dash()
app.layout = html.Div([
# display time elapsed
html.H1(id='live-update-text'),
# for live updating
dcc.Interval(
id='interval-component',
interval=1000, # 1000 milliseconds
n_intervals=0
)
])
# call back function
#app.callback(Output('live-update-text', 'children'),
[Input('interval-component', 'n_intervals')])
def update_layout(n):
msg = sec_transform(n)
return msg
if __name__ == '__main__':
app.run_server()