Disable "Submit" button for 30 seconds in Python + Dash - python

I want to stop the user from pressing the "Submit" button for 30 seconds, after they have pushed it in the script below. How would I go about doing this? This is how my code looks currently:
import dash
from dash.dependencies import Input, Output, State
import dash_core_components as dcc
import dash_html_components as html
app = dash.Dash()
app.layout = html.Div([
dcc.Input(id='my-id', value='initial value', type="text"),
html.Button('Submit', id='button'),
html.Div(id='my-div')
])
#app.callback(
Output(component_id='my-div', component_property='children'),
[Input('button', 'n_clicks')],
state=[State(component_id='my-id', component_property='value')]
)
def update_output_div(n_clicks, input_value):
return 'You\'ve entered "{}" and clicked {} times'.format(input_value, n_clicks)
if __name__ == '__main__':
app.run_server()
Does anyone know how I can stop users from pushing the button for 30 seconds?
Thank in advance.
EDIT 15/08/2018 9:30AM GMT RESPONSE TO stevepastelan:
import dash
from dash.dependencies import Input, Output, State
import dash_core_components as dcc
import dash_html_components as html
app = dash.Dash()
app.layout = html.Div([
dcc.Input(id='my-id', value='initial value', type="text"),
html.Button('Submit', id='button'),
html.Div([dcc.Interval(
id='interval-component',
interval=1 * 3000, # in milliseconds
n_intervals=0
)]),
html.Div(id='my-div')
])
#app.callback(
Output(component_id='my-div', component_property='children'),
[Input('button', 'n_clicks')], [Input('interval-component', 'n_intervals')],
state=[State(component_id='my-id', component_property='value')]
)
def update_output_div(n_clicks,n_intervals, input_value):
return 'You\'ve entered "{}" and clicked {} times'.format(input_value, n_clicks)
if __name__ == '__main__':
app.run_server()
EDIT 15/08/2018 16:22PM WROTE SIMPLER SCRIPT WITH THE AN EDITED CALLBACK BUT IT DOESNT WORK:
import dash
from dash.dependencies import Input, Output, State
import dash_core_components as dcc
import dash_html_components as html
app = dash.Dash()
app.layout = html.Div([
dcc.Input(id='my-id', value='initial value', type="text"),
html.Button('Submit', id='button'),
html.Div([dcc.Interval(
id='interval-component',
interval=1 * 3000, # in milliseconds
n_intervals=0
)]),
html.Div(id='my-div')
])
#app.callback(
Output(component_id='my-div', component_property='children'),
[Input('button', 'n_clicks')], [Input('interval-component', 'n_intervals')],
state=[State(component_id='my-id', component_property='value')]
)
def update_output_div(n_clicks,n_intervals, input_value):
return 'You\'ve entered "{}" and clicked {} times'.format(input_value, n_clicks)
if __name__ == '__main__':
app.run_server()

Updated answer
Okay, I managed to implement my own suggestion, but it was not trivial and still has quirks.
Complicating factors were:
Dash does not permit two callbacks to target the same Output
There is no good way to track which Input or Event triggered your callback. Workarounds generally involve tracking the number of clicks per button (see https://github.com/plotly/dash-html-components/pull/37 as an example).
Disabling a timer via disable=True or max_requests=0 appears to be permanent. Once I stopped a timer in this way, I could not restart it with either disable=False or max_requests=1000.
Issues:
In this solution, update_output_div() gets called twice -- but you can tell the difference between the two by measuring the number of button clicks to the previous count, so you can keep it from submitting your data twice.
Timeouts of less than 100ms won't work. I had to split the delay timer for my method to work, so I chose 100 and (1000 * BUTTON_PRESS_LOCKOUT_SECONDS)-100 as the two timer durations. In principle, you could split them evenly half and half. I don't know if there are any problems with using a low timeout when working over the network (I did my testing on localhost).
Inspiration drawn from:
https://community.plot.ly/t/how-to-turn-off-interval-event/5565/3
https://github.com/plotly/dash-recipes/blob/master/toggle-interval.py
import json
import datetime
import dash
from dash.dependencies import Input, Output, State, Event
import dash_core_components as dcc
import dash_html_components as html
BUTTON_PRESS_LOCKOUT_SECONDS = 10 # seconds
app = dash.Dash()
app.config['suppress_callback_exceptions']=True
def serve_layout():
return html.Div([
dcc.Input(id='my-id', value='initial value', type="text"),
html.Button('Submit', id='button'),
html.Div(
[
dcc.Interval(id='interval-component', disabled=True)
, dcc.Interval(id='interval-sync-component', disabled=True)
]
, id='interval-container'
),
html.Div("", id='my-div'),
html.Div(json.dumps({'n_clicks':0, 'n_previous_clicks':0}), id='local_data'),
html.Div('??', id='button-status'),
])
app.layout = serve_layout
# Track button clicks
#app.callback(
output=Output(component_id='local_data', component_property='children'),
inputs=[Input('button', 'n_clicks')],
state=[State('local_data', 'children')],
events=[Event('interval-sync-component', 'interval')]
)
def track_clicks(n_clicks, local_data_json):
if n_clicks is None:
n_clicks = 0
local_data = json.loads(local_data_json)
n_previous_clicks = local_data['n_clicks']
# Update local data with the new click data
local_data.update(**{'n_clicks': n_clicks, 'n_previous_clicks': n_previous_clicks})
# local_data.update(**{'n_clicks': n_clicks, 'n_previous_clicks': n_previous_clicks})
return json.dumps(local_data)
# When the button click count is updated, submit
#app.callback(
output=Output(component_id='my-div', component_property='children'),
inputs=[Input('local_data', 'children')],
state=[State(component_id='my-id', component_property='value'), State('my-div', 'children')]
)
def update_output_div(local_data_json, input_value, current_state):
local_data = json.loads(local_data_json)
n_clicks = local_data['n_clicks']
n_previous_clicks = local_data['n_previous_clicks']
# Real submit
if n_clicks > n_previous_clicks:
return 'You\'ve entered "{}" and clicked {} times ({})'.format(
input_value
, n_clicks if n_clicks is not None else 0
, datetime.datetime.now()
)
# Not a real submit, but function is called an extra time as a side effect of the timer nonsense below.
else:
return '*' + current_state
# Start (or stop) the timer
#app.callback(
output=Output('interval-container', 'children'),
inputs=[Input('local_data', 'children')],
state=[State('button', 'disabled')],
events=[Event('interval-component', 'interval')]
)
def start_timer(local_data_json, button_is_disabled):
local_data = json.loads(local_data_json)
n_clicks = local_data['n_clicks']
n_previous_clicks = local_data['n_previous_clicks']
children=[]
if n_clicks > n_previous_clicks:
sync_timer = dcc.Interval(
id='interval-sync-component',
interval=100, # in milliseconds
)
children.append(sync_timer)
if button_is_disabled:
main_timer = dcc.Interval(
id='interval-component',
interval=(1000 * BUTTON_PRESS_LOCKOUT_SECONDS)-100, # in milliseconds
)
children.append(main_timer)
return children
# Enable the button whenever the timer interval is triggered or disable it when the button is pressed
#app.callback(
output=Output('button', 'disabled'),
inputs=[Input('button', 'n_clicks')],
state=[State('local_data', 'children')],
events=[Event('interval-component', 'interval')]
)
def toggle_button_disabled_state(n_clicks, local_data_json):
local_data = json.loads(local_data_json)
# n_clicks = local_data['n_clicks']
if n_clicks is None:
n_clicks = 0
n_previous_clicks = local_data['n_previous_clicks']
# We got here via button click, so disable the button
if n_clicks > n_previous_clicks:
return True
# We got here via timer expiration, so enable the button
else:
return False # enable the button
# Report on the button status
#app.callback(
output=Output('button-status', 'children'),
inputs=[Input('button', 'disabled')]
)
def update_button_status(disabled):
if disabled:
return 'Disabled submit button for {} seconds'.format(BUTTON_PRESS_LOCKOUT_SECONDS)
else:
return 'Submit button enabled'
if __name__ == '__main__':
app.run_server()
Original answer
You can trigger actions to be taken on the page based on a timer using dash_core_components.Interval. There are some examples here: https://dash.plot.ly/live-updates
You could initialize your interval component with n_intervals = 0, and then make your submit button disable itself and set n_intervals = 1. Then write a callback on the interval that re-enables the button.

Related

Combine DateRangePicker and Dropdown in Plotly Dash

I'm trying to use DateRangePicker and Dropdown to callback a dash_table. Actually I tried to call dropdown option first and then call dash_table after calling options, but it didn't work.
I don't know how to add a second call back that is based on dropdown options.
Below is my code:
import pandas as pd
import numpy as np
from datetime import datetime
import plotly.express as px
import dash_html_components as html
import dash_core_components as dcc
from dash.dependencies import Input, Output
import dash_table
import dash_bootstrap_components as dbc
from datetime import datetime as dt
lst_str_cols = ['BR_CD']
dict_dtypes = {x : 'str' for x in lst_str_cols}
df = pd.read_excel('https://github.com/hoatranobita/test_datepicker/blob/main/MD6200(20150101_20201231)_2%20(1).xlsx?raw=true')
df['ISSUE_DATE_2'] = df['ISSUE_DATE']
df['ISSUE_DATE'] = pd.to_datetime(df['ISSUE_DATE'],infer_datetime_format=True,errors='coerce')
df.set_index('ISSUE_DATE',inplace=True)
app = dash.Dash(__name__,external_stylesheets=[dbc.themes.LUX])
branches = df['BR_CD'].unique().tolist()
app.layout = html.Div([
dbc.Row([
dbc.Col([html.H5('Drop Down',className='text-center'),
dcc.DatePickerRange(
id='my-date-picker-range', # ID to be used for callback
calendar_orientation='horizontal', # vertical or horizontal
day_size=39, # size of calendar image. Default is 39
end_date_placeholder_text="Return", # text that appears when no end date chosen
with_portal=False, # if True calendar will open in a full screen overlay portal
first_day_of_week=0, # Display of calendar when open (0 = Sunday)
reopen_calendar_on_clear=True,
is_RTL=False, # True or False for direction of calendar
clearable=True, # whether or not the user can clear the dropdown
number_of_months_shown=1, # number of months shown when calendar is open
min_date_allowed=dt(2015, 1, 1), # minimum date allowed on the DatePickerRange component
max_date_allowed=dt.today(), # maximum date allowed on the DatePickerRange component
initial_visible_month=dt.today(), # the month initially presented when the user opens the calendar
#end_date_placeholder_text='DD-MM-YYYY',
start_date=dt(2015, 1, 1).date(),
end_date=dt.today().date(),
display_format='DDMMYYYY', # how selected dates are displayed in the DatePickerRange component.
month_format='MMMM, YYYY', # how calendar headers are displayed when the calendar is opened.
minimum_nights=0, # minimum number of days between start and end date
persistence=True,
persisted_props=['start_date'],
persistence_type='session', # session, local, or memory. Default is 'local'
updatemode='singledate' # singledate or bothdates. Determines when callback is triggered
)],width={'size':4,"offset":0,'order':1}),
dbc.Col([html.H5('Drop Down',className='text-center'),
dcc.Dropdown(
id='filter_dropdown',placeholder="Please select branch code",
options=[{'label':br, 'value':br} for br in branches],
value = [],
multi=True,
disabled=False,
clearable=True,
searchable=True)],width={'size':2,"offset":0,'order':1}),
]),
dbc.Row([
dbc.Col([html.H5('Disbursement List',className='text-center'),
dash_table.DataTable(id='table-container',data=[],
columns=[{"name":i,"id":i,'type':'numeric'} for i in df.columns],
style_table={'overflow':'scroll','height':500},
style_cell={'textAlign':'center'})
],width={'size':12,"offset":0,'order':1})
])
])
#app.callback(Output('table-container', 'data'),
[Input('my-date-picker-range', 'start_date'),
Input('my-date-picker-range', 'end_date')])
def update_data(start_date, end_date):
data = df.loc[start_date: end_date]
return data.to_dict('rows')
if __name__ == '__main__':
app.run_server(debug=False, dev_tools_ui=False,port=8051)
You can include both filters in your callback as follows:
#app.callback(Output('table-container', 'data'),
[Input('my-date-picker-range', 'start_date'),
Input('my-date-picker-range', 'end_date'),
Input('filter_dropdown', 'value')])
def update_data(selected_start_date, selected_end_date, selected_branches):
# filter the data frame based on the DatePickerRange selection
data = df[(df.index >= selected_start_date) & (df.index <= selected_end_date)]
# filter the data frame based on the Dropdown selection
if selected_branches != []:
data = data[data['BR_CD'].isin(selected_branches)]
return data.to_dict(orient='records')

Dashboard with DASH - Argument issues dcc.graph()

I'm trying to create a dashboard in Pycharm using dash. Here is the error I keep receiving,
html.Div(dcc.Graph(id='line-plot')),
TypeError: Graph() takes no arguments
And below is a snippet of my code where the error is being found (bottom of code). This code ran fine and I was about to populate the dashboard without receiving any errors inside IBM's python environment. I'm assuming I have to tweak something
# TASK 3 - UPDATE LAYOUT COMPONENETS
# html.H1 tag for title , style, and overall font size
# html.Div & dcc.Input() tag to set inputs of the dashboard
# Update output componenent 2nd html.Div to layout the graph dcc.Graph()
app.layout = html.Div(children=[html.H1('Airline Performance Dashboard',
style={'textAlign': 'center', 'color': '#503D36',
'font-size': 40}),
html.Div(["Input Year: ", dcc.Input(id='input-year', value='2010',
type='number',
style={'height': '50px', 'font-size': 35}), ],
style={'font-size': 40}),
html.Br(),
html.Br(),
html.Div(dcc.Graph(id='line-plot')),
])
Here is the rest of the code,
# TASK 4 - ADD APPLICATION CALL BACK FUNCTION and outputs / inputs
# add callback decorator
#app.callback(Output(component_id='line-plot', component_property='figure'),
Input(component_id='input-year', component_property='value'))
# Add computation to callback function and return graph
def get_graph(entered_year):
# Select 2019 data
df = airline_data[airline_data['Year'] == int(entered_year)]
# Group the data by Month and compute average over arrival delay time.
line_data = df.groupby('Month')['ArrDelay'].mean().reset_index()
# TASK 5 - UPDATE CALL BACK FUNCTION go.Figure(data=) and update fig.update_layout()
fig = go.Figure(
data=go.Scatter(x=line_data['Month'], y=line_data['ArrDelay'], mode='lines', marker=dict(color='green')))
fig.update_layout(title='Month vs Average Flight Delay Time', xaxis_title='Month', yaxis_title='ArrDelay')
return fig
# Run the app
if __name__ == '__main__':
app.run_server()
Its safe to say I need an adult.
have added import and simulation of data frame
all other code runs without issue on plotly 5.1.0
import dash_core_components as dcc
import dash_html_components as html
from dash.dependencies import Input, Output, State
import plotly.graph_objects as go
from jupyter_dash import JupyterDash
import numpy as np
app = JupyterDash(__name__)
# simulate data...
dr = pd.date_range("1-jan-2010", freq="W", periods=200)
airline_data = pd.DataFrame({"Year":dr.year, "Month":dr.month, "ArrDelay":np.random.uniform(2,5,len(dr))})
# TASK 3 - UPDATE LAYOUT COMPONENETS
# html.H1 tag for title , style, and overall font size
# html.Div & dcc.Input() tag to set inputs of the dashboard
# Update output componenent 2nd html.Div to layout the graph dcc.Graph()
app.layout = html.Div(children=[html.H1('Airline Performance Dashboard',
style={'textAlign': 'center', 'color': '#503D36',
'font-size': 40}),
html.Div(["Input Year: ", dcc.Input(id='input-year', value='2010',
type='number',
style={'height': '50px', 'font-size': 35}), ],
style={'font-size': 40}),
html.Br(),
html.Br(),
html.Div(dcc.Graph(id='line-plot')),
])
# TASK 4 - ADD APPLICATION CALL BACK FUNCTION and outputs / inputs
# add callback decorator
#app.callback(Output(component_id='line-plot', component_property='figure'),
Input(component_id='input-year', component_property='value'))
# Add computation to callback function and return graph
def get_graph(entered_year):
# Select 2019 data
df = airline_data[airline_data['Year'] == int(entered_year)]
# Group the data by Month and compute average over arrival delay time.
line_data = df.groupby('Month')['ArrDelay'].mean().reset_index()
# TASK 5 - UPDATE CALL BACK FUNCTION go.Figure(data=) and update fig.update_layout()
fig = go.Figure(
data=go.Scatter(x=line_data['Month'], y=line_data['ArrDelay'], mode='lines', marker=dict(color='green')))
fig.update_layout(title='Month vs Average Flight Delay Time', xaxis_title='Month', yaxis_title='ArrDelay')
return fig
app.run_server(mode="inline")

forcing initial callback despite dash.PreventUpdate preventing it from other function

When a dash app is started, all callbacks should be fired according to the dash documentation. But when callbacks are linked (ones output is the other ones input) the initial callback of the latter is prevented when the first raises a dash.exceptions.PreventUpdate or returns a dash.no_update.
Although I can somehow follow that logic, it was not clear for me that this would happen, and I am interested in reading somewhere the reasons behind this, although I have no idea where.
More practical, I would like to fire the initial callback nevertheless, is this possible? The code below is a minimal example of the problem:
import dash
import dash_core_components as dcc
import dash_html_components as html
from dash.dependencies import Input, Output, State
import plotly.graph_objects as go
import numpy as np
from time import sleep
app = dash.Dash(__name__)
app.layout = html.Div([
html.Button('test', id='button'),
dcc.Dropdown(id='super', options=[{'label':x, 'value':x} for x in ['test1', 'test2']]),
dcc.Dropdown(id='under', options=[{'label':x, 'value':x} for x in ['under1', 'under2']]),
dcc.Graph(id='graph'),
])
#app.callback(
Output('super', 'value'),
Input('button', 'n_clicks'),
prevent_initiall_call=True
)
def set_super_value(nclicks):
if nclicks is None:
return dash.no_update
return 'test2'
#app.callback(
Output('under', 'value'),
Input('super', 'value')
)
def set_under_value(val):
sleep(5)
return 'under1'
#app.callback(
Output('graph', 'figure'),
Input('under', 'value'),
)
def make_figure(val):
sleep(5)
if val == 'under1':
return go.Figure(go.Scatter(x=np.arange(0,1000), y=np.sin(np.arange(0,1000)/100)))
else:
return go.Figure(go.Scatter(x=np.arange(0,1000), y=np.cos(np.arange(0,1000)/100)))
if __name__ == '__main__':
app.run_server(debug=True)
This is the initial state:
And this is how I would like it to be:
In this simple example I can always set this initial value as the 'value' option in the initial app.layout variable, but the problem in my actual application makes this a suboptimal solution in my eyes.
* Edits made to the code to make specify the problem a little more specific. Code still works and screenshots still apply (but

Plotly Dash: How to store input-data in multipage app?

I hope that someone can help me on the problem i'm facing at the moment. I'm building a 3-pages dash app, and I'm struggling to use the dcc.store component.
On the first page there is an input-field where users can input their name. I would like to store the name so that I can use it on page 2. But for some reason I don't get it working the way I want it to. Here's the code:
import dash
import dash_html_components as html
import dash_bootstrap_components as dbc
import dash_core_components as dcc
from dash.dependencies import Input,Output,State
from dash.exceptions import PreventUpdate
#############################################
########### variables & settings ############
#############################################
## app variabelen
app = dash.Dash(external_stylesheets=[dbc.themes.BOOTSTRAP])
app.config.suppress_callback_exceptions = True
len_data = 3500
#############################################
########### setup app-components ############
#############################################
## header met paginaselecties
navbar = dbc.NavbarSimple(
children=[
dbc.NavItem(dbc.NavLink("Login",href="/login"),id='nav_login'),
dbc.NavItem(dbc.NavLink("Recode",href="/recode"),id='nav_recode'),
],
brand="recode",
brand_href="/",
color="primary",
)
#############################################
############### layout pages ################
#############################################
## layout pagina 1
page_1 = html.Div([navbar,
html.Div([
html.Div([
dbc.FormGroup([dbc.Label("Name"),
dbc.Input(id="name",
type="text",
placeholder="...")],
className="m-3"),
html.Div(id='output_name')
],style={'width':'40%'}),
]),
])
## layout pagina 2
page_2 = html.Div([navbar,
html.Div(id='name_output')
])
#############################################
################ layout app #################
#############################################
app.layout = html.Div([
dcc.Store(id='session',
data=[{'name':'Leeg'}]),
dcc.Location(id='url',refresh=False),
html.Div(id='page-content')
])
#############################################
############# callbacks routing #############
#############################################
#app.callback(Output('page-content','children'),
[Input('url','pathname')])
def display_page(pathname):
if pathname == '/login':
return page_1
elif pathname == '/recode':
return page_2
#app.callback(Output('nav_login','active'),
[Input('url','pathname')])
def set_page_1_active(pathname):
return pathname == '/login'
#app.callback(Output('nav_recode','active'),
[Input('url','pathname')])
def set_page_2_active(pathname):
return pathname == '/recode'
#############################################
################# callbacks ################
#############################################
#app.callback(
Output('session','data'),
[Input('name','value')])
def return_name(value):
if value is None:
raise PreventUpdate
data = [{'name':value}]
return data
#app.callback(
Output('name_output','data'),
[Input('session','data')])
def print_name(name):
return name
#app.callback(
Output('slider_output','children'),
[Input('slider_select','value')])
def update_slider(value):
return "You've selected {} from total ({})".format(value,len_data)
if __name__ == "__main__":
app.run_server(debug=True,port=8050)
Hopefully someone can help me. I can allways use the hidden-Div scenario, but I really want to use the dcc.store so that I can use it in the future as well...
Thanks,
The dcc.Store() component is working fine, the source of the problem is the print_name() callback:
In the callback you are referencing the 'data' property of the html.Div() with id='name_output' instead of the 'children' property which is where the contents of the html.Div() are actually stored; see the Dash documentation for more details: https://dash.plotly.com/dash-html-components/div.
In order to print the input text on the screen you would need to extract it from the data saved in the dcc.Store() component as follows name = data[0]['name'] given that data = [{'name': value}].
If you update the print_name() callback in line with the code below your app should work.
#app.callback(
Output('name_output', 'children'),
[Input('session', 'data')])
def print_name(data):
name = data[0]['name']
return name

Dash(Python) - thicker ring in gauge meter of dash-daq

The ring that dash_daq.Gauge outputs is too thin, as you can see from the picture below.
I would like to have thicker ring. I couldn’t find css element under ‘inspect element’ to increase the thickness of ring. How do i go about doing this?
Just create an assets folder and place there your css file e.g. "styles.css" and it works fine (Dash v1.6.0)
styles.css:
circle {
stroke-width: 20px;
}
app.py:
import dash
import dash_daq as daq
import dash_core_components as dcc
import dash_html_components as html
app = dash.Dash(__name__, assets_folder = 'assets', include_assets_files = True)
app.layout = html.Div([
daq.Gauge(
id='my-gauge',
label="Default",
value=6,
style={'display': 'block' }
),
dcc.Slider(
id='my-gauge-slider',
min=0,
max=10,
step=1,
value=5
),
])
#app.callback(
dash.dependencies.Output('my-gauge', 'value'),
[dash.dependencies.Input('my-gauge-slider', 'value')]
)
def update_output(value):
return value
if __name__ == '__main__':
app.run_server(debug=True)
gauge meter is made up of svg tag. To get an idea i will show the screen shot
Try changing the stroke-width attr value to change the thinkness. i hope this will help you to get inital idea.

Categories