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

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

Related

dash drawing different graph when clicking on different button

I want to draw graph A when clicking on button A and graph B when clicking on button B, if either graphs is drawn and I click on either button I want the page to be updated with the new graph, I'm using dash and plotly, here the pseudo code
#app.callback([Output("graph-content", "children")],[Input("KPI_gauge","n_clicks")])
def kpi_gauge(v):
if v == None:
raise PreventUpdate
fig1 = KPIGauge(88.7,102)
return [dcc.Graph(figure=fig1)]
#app.callback([Output("graph-content", "children")],[Input("comparaison_graph","n_clicks")]
def comparaison_graph(v):
if v == None:
raise PreventUpdate
fig = ComparaisonGraph()
return [dcc.Graph(figure=fig)]
but I get this error dash duplicate callback output, how to resolve this error?
https://dash.plotly.com/callback-gotchas A component/property pair can only be the Output of one callback. Your code broke this rule, graph-content was the output of two callbacks
this code demonstrates a way to output plotly figure on first click and update data in graph on subsequent clicks
import dash_core_components as dcc
import dash_html_components as html
from dash.dependencies import Input, Output, State
from dash.exceptions import PreventUpdate
import dash
from jupyter_dash import JupyterDash
import random
import plotly.graph_objects as go
import plotly.express as px
import pandas as pd
import numpy as np
# Build App
app = JupyterDash(__name__)
app.layout = html.Div(
[
html.Button("KPI", id="KPI_gauge", n_clicks=0),
html.Button("Graph", id="comparaison_graph", n_clicks=0),
html.Div(id="graph-content"),
]
)
#app.callback(
Output("graph-content", "children"),
Input("KPI_gauge", "n_clicks"),
Input("comparaison_graph", "n_clicks"),
State("graph-content", "children"),
)
def update_graph(kpi_but, graph_but, content):
if not kpi_but and not graph_but:
raise PreventUpdate
ctx = dash.callback_context
button = ctx.triggered[0]["prop_id"].split(".")[0]
fig_id = f"{button}_fig"
if button=="KPI_gauge":
if content and content["type"]=="Graph" and content["props"]["id"]==fig_id: # update
fig = content["props"]["figure"]
fig["data"][0]["value"] = random.randint(0,400)
else: # create
fig = go.Figure(go.Indicator(mode="gauge+number",value=270,title={"text": "KPI"},))
elif button=="comparaison_graph":
if content and content["type"]=="Graph" and content["props"]["id"]==fig_id: # update
fig = content["props"]["figure"]
fig["data"][0]["y"] = np.sort(np.random.uniform(1,5,365*2))
else: # create
df = pd.DataFrame({"date":pd.date_range("1-aug-2019", periods=365*2), "value":np.linspace(1,5,365*2)})
fig = px.line(df, x="date", y="value")
else:
raise PreventUpdate
return dcc.Graph(id=fig_id, figure=fig)
app.run_server(mode="inline", port=8051)

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

Null is not an object (evaluating ‘n.layout’) in Dash Plotly

I am developing dashboard using Dash Plotly and I am getting an error when I click tabs.
The error says null is not an object (evaluating ‘n.layout’)
(This error originated from the built-in JavaScript code that runs Dash apps. Click to see the full stack trace or open your browser’s console.)"
Can any one help me to solve this problem?
My code is found below.
import dash
import dash_core_components as dcc
import dash_html_components as html
import dash_daq as daq
from dash.dependencies import Input, Output
import plotly.graph_objs as go
import pandas as pd
import numpy as np
from copy import copy
import dash_table
import json
import base64
import plotly.express as px
#Data
errors = pd.read_csv(r’/Users/kapital/Documents/ABCD/PM/errors.csv’)
external_stylesheets = [‘https://codepen.io/chriddyp/pen/bWLwgP.css’]
app = dash.Dash(name, external_stylesheets=external_stylesheets)
first_graph = dcc.Graph(id=‘graph1’,style={‘borderBottom’: ‘thin lightgrey solid’,‘padding’: ‘10px 5px’})
#, animate = True
content_tab_1 = html.Div(children = [
html.Div(first_graph, style = {‘vertical-align’:‘center’, ‘horizontal-align’:‘center’})
],
style={‘width’: ‘87%’})
app.layout = html.Div([
dcc.Tabs(id='tabs-example', value='tab-1', children=[
dcc.Tab(label='Tab one', value='tab-1',
children =[content_tab_1]),
dcc.Tab(label='Tab two', value='tab-2'),
]),
html.Div(id='tabs-example-content')
])
#app.callback(Output(‘graph1’, ‘figure’),
Input(‘tabs-example’, ‘value’))
def render_content(tab):
if tab == ‘tab-1’:
err_count = pd.DataFrame(errors['errorID'].value_counts().reset_index().values, columns=["Error", "Count"])
err_count = err_count.sort_index(axis = 0, ascending=True)
fig = px.bar(err_count, x = 'Error', y = 'Count')
return fig
# return html.Div([
# html.H3('Tab content 1')
# ])
if name == ‘main’:
app.run_server(debug=True)
The problem is that your callback function does not have an else to its conditional statement. When the user selects the second tab, that callback has no alternate path, and returns None, which gives you the error you see. I did this to fix it, but you'll probably want something more:
#app.callback(Output('graph1', 'figure'),
Input('tabs-example', 'value'))
def render_content(tab):
if tab == 'tab-1':
err_count = pd.DataFrame.from_dict(dict(
Error=[1, 2, 3, ],
Count=[5, 6, 7])
)
err_count = err_count.sort_index(axis=0, ascending=True)
fig = px.bar(err_count, x='Error', y='Count')
return fig
else:
return {}

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

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.

Running multi-paged Plotly-Dash app with callbacks and graphs within Flask

Intro:
I already have a multi-page Dash app running with each page into a separate layout file and is callable from main index page.
What works well?
Running a standalone Dash app, ($python index.py), index page is shown with other entries and each link works well, with their graphs and callbacks.
'''
index.py : relevant sections
'''
from appc import app, server
import page1c, page2c, page3c
app.layout = html.Div([
dcc.Location(id='url', refresh=False),
html.Div(id='page-content')
])
...
...
..
index_page = html.Div([ ....
# Similar to calling in flask_app.py
------------------------------------------------------------
'''
appc.py : relevant sections
'''
app = dash.Dash('auth')
auth = dash_auth.BasicAuth(
app,
VALID_USERNAME_PASSWORD_PAIRS
)
server = app.server
app.config.suppress_callback_exceptions = True
...
..
'''
What doesn't work well?
A: Trying to use existing Dash App within a Flask app ($python flask_app.py) but having issue where only HTML content (from layout is shown) but callbacks aren't triggering if Dash layouts are defined in separate files.
Why?
A: Planning to use Flask for main web site and features and Dash for interactive graphs and HTML layout.
Attempted solution:
Below is the code from flask_app.py and i've commented to my best ability.
'''
flask_app.py : Attempt to run dash and flask based routes in one instance.
'''
from flask import Flask, render_template
from dash import Dash
from dash.dependencies import Input, State, Output
import dash_core_components as dcc
import dash_html_components as html
import json
import plotly
import pandas as pd
import numpy as np
server = Flask(__name__)
########################################################################
#server.route('/graph') # Interactive Dash Graph in predefined HTML
def index():
rng = pd.date_range('1/1/2011', periods=7500, freq='H')
ts = pd.Series(np.random.randn(len(rng)), index=rng)
graphs = [
dict(
data=[
dict(
x=[1, 2, 3],
y=[10, 20, 30],
type='scatter'
),
],
layout=dict(
title='first graph'
)
),
dict(
data=[
dict(
x=[1, 3, 5],
y=[10, 50, 30],
type='bar'
),
],
layout=dict(
title='second graph'
)
),
dict(
data=[
dict(
x=ts.index, # Can use the pandas data structures directly
y=ts
)
]
)
]
# Add "ids" to each of the graphs to pass up to the client
# for templating
ids = ['Graph-{}'.format(i) for i, _ in enumerate(graphs)]
# Convert the figures to JSON
# PlotlyJSONEncoder appropriately converts pandas, datetime, etc
# objects to their JSON equivalents
graphJSON = json.dumps(graphs, cls=plotly.utils.PlotlyJSONEncoder)
return render_template('layouts/graph.html',
ids=ids,
graphJSON=graphJSON)
########################################################################
#server.route('/hello') # Static predefined HTML
def hello_index():
return render_template('hello.html',)
########################################################################
app = Dash(server=server, url_base_pathname='/dash') # Interactive Dash input box with callback.
app.layout = html.Div([
html.Div(id='target'),
dcc.Input(id='input', type='text', value=''),
html.Button(id='submit', n_clicks=0, children='Save')
])
#app.callback(Output('target', 'children'), [Input('submit', 'n_clicks')],
[State('input', 'value')])
def callback(n_clicks, state):
return "callback received value: {}".format(state)
######################################################################
app = Dash(__name__, server=server, url_base_pathname='/dashed') #Another Bash Graph inline, no callbacks.
app.layout = html.Div(children=[
html.Div(children='''
Dash: A web application framework for Python
'''),
dcc.Graph(
id='example-graph',
figure={
'data': [
{'x': [1, 2, 3], 'y': [4, 1, 2], 'type': 'bar', 'name': 'SF'},
{'x': [1, 2, 3], 'y': [2, 4, 6], 'type': 'bar', 'name': 'Montreal'},
],
'layout': {
'title': 'Dash Data Visualization'
}
}
)
])
########################################################################
'''
Content from 'index.py' : Check above.
page1c, page2c, page3c are dash separate layout files for a multipage website with dash, which is working perfect.
These are called in 'index.py' (main page) respectively as below.
Running 'python index.py' (standalone dash instance), all the interactive pages are responsive and plot the data (with callbacks) they're intended to.
But running with flask, pages only show HTML content, sliders and dropdown boxes, but the backend processes aren't triggering so no graphs are generated.
'''
# Note: 'index_page' is the layout with 3 links to above items.
# All 3 files have multiple layout (with graphs and callbacks), different files for everyone to keep it simple and less cluttered.
import page1c, page2c, page3c
from index import index_page
d_app = Dash(server=server, url_base_pathname='/', )
d_app.layout = html.Div([
html.Div(id='page-content'),
dcc.Location(id='url', refresh=True),
])
#d_app.callback(Output('page-content', 'children'),
[Input('url', 'pathname')])
def display_page(pathname):
if pathname == '/page1':
return page1c.layout
elif pathname == '/page2':
return page2c.layout
elif pathname == '/page3':
return page3c.layout
else:
return index_page
######################################################################
if __name__ == '__main__':
app.run_server(port=9999, debug=True)

Categories