Related
I created a huge Graphviz Network, which I now want to spice up with some interactivity. For this goal I discovered the package dash_interactive_graphviz. From my understanding I can simple provide my existing graph, but I'm already failing to execute the provided sample (see below):
import dash_interactive_graphviz
dot_source = """
digraph {
node[style="filled"]
a ->b->d
a->c->d
}
"""
dash_interactive_graphviz.DashInteractiveGraphviz(
id="graph",
dot_source=dot_source
)
The package itself and all requirements are fulfilled. I run the sample code from above in Visual Studio Code, but nothing happens (no output, no message, no error).
Anybody who can point me in the right direction? Thanks.
You are in the right direction, just need a few steps. I think you can check out this example here to run your file:
https://github.com/BusinessOptics/dash_interactive_graphviz/blob/master/usage.py
import dash_interactive_graphviz
import dash
from dash.dependencies import Input, Output
import dash_html_components as html
import dash_core_components as dcc
app = dash.Dash(__name__)
initial_dot_source = """
digraph {
node[style="filled"]
a ->b->d
a->c->d
}
"""
app.layout = html.Div(
[
html.Div(
dash_interactive_graphviz.DashInteractiveGraphviz(id="gv"),
style=dict(flexGrow=1, position="relative"),
),
html.Div(
[
html.H3("Selected element"),
html.Div(id="selected"),
html.H3("Dot Source"),
dcc.Textarea(
id="input",
value=initial_dot_source,
style=dict(flexGrow=1, position="relative"),
),
html.H3("Engine"),
dcc.Dropdown(
id="engine",
value="dot",
options=[
dict(label=engine, value=engine)
for engine in [
"dot",
"fdp",
"neato",
"circo",
"osage",
"patchwork",
"twopi",
]
],
),
],
style=dict(display="flex", flexDirection="column"),
),
],
style=dict(position="absolute", height="100%", width="100%", display="flex"),
)
#app.callback(
[Output("gv", "dot_source"), Output("gv", "engine")],
[Input("input", "value"), Input("engine", "value")],
)
def display_output(value, engine):
return value, engine
#app.callback(Output("selected", "children"), [Input("gv", "selected")])
def show_selected(value):
return html.Div(value)
if __name__ == "__main__":
app.run_server(debug=True)
I have a dash plotly python jupyter notebook app (music) that takes 5 rows as a sample from a bigger dataframe using df_sample= df.sample(5). It has a quiz with inputs to compare the response of the users with the df_sample and take points depending on the users response and the df_sample values.
When I test it locally the code works perfectly also when I run it locally through heroku local.
But When I deploy to Heroku in production the code fail because df_sample will takes two differents set of values, when the app is updating during the callbacks and then comparison between the users response and the the df_sample dataframe fails.
example:
df_sample('name')=('c','f', 'g', 'b', 'e')
when i use locally this is constant during the callbacks & every works fine
df_sample('name')=('c','f', 'g', 'b', 'e')
df_sample('name')=('b','i', 'l', 'm', 'o')
when I deploy to heroku df_sample get 2 different set of vales during the callbacks and the app fails
here an extract of the code:
Import requests
Import pandas as pd
Import dash
from dash.dependencies import Input, Output
Import dash_html_components as html
Import dash_core_components as dcc
from dash import Dash
from dash import dcc
from dash import html
url1='url'
download = github_session.get(url1).content
df_link = pd.read_csv(io.StringIO(download.decode('latin-1')), sep=";",
error_bad_lines=False, warn_bad_lines=False, encoding='latin-1')
server = app.server
df_sample=df_link.sample(5)
df_sample.reset_index(inplace=True)
compositors= dbc.Col(
dcc.Dropdown(
id="compositor",
options=[
{"label": str(i), "value": i} for i in autors
],
value="",
clearable=False,
)
dbc.Row(
[
dbc.Label("5-look The correct answer is:",
width="auto"),
dbc.Col(
dbc.Input(id="name", value=compositor_result, type="text",size="lg")
)
]
)
dbc.Col(dbc.Button("4-Submmit & play next record", color="danger",
id="example-button",
n_clicks=0)
),
dbc.Col(
dbc.Input(id="result", value=result, type="text",size="lg")
)
app.layout = ....
#app.callback(
Output("result", "value"),
Output("compositor_result", "value"),
Input('example-button', 'n_clicks'),
State("compositor", "value"),
def update_line_chart(n_clicks, compositor):
global value1
global df_sample
if (n_clicks==None):
raise prevent_update
if df:_sample[n_clicks-1]==compositor:
result=value1+15
else: value1=value1
compositor_result=df_sample.name[n_click-1]
return result, compositor_result
if __name__ == '__main__':
app.run_server(debug=True, use_reloader=False)
Thanks
I am relatively new to Dash, and thought I understood the callbacks pretty well. However, I am now in a situation where it seems I need to have all callbacks within one callback, as my program works fine when the one is called.
When I have multiple callbacks, they work fine individually; meaning that if I comment one out the callback works as desired. I am able to get the correct functionality using context.triggered, but I do not want to have everything in one massive callback, and I feel like this is an issue with my understanding. I have tried limiting to one input on the callback, but that still does not work. Am I passing the whole app layout to the callback?
If I am, how do I limit what is being passed beyond the ids? How
can callbacks?
An adapted working example is below:
import dash
import dash_core_components as dcc
import dash_html_components as html
import pandas as pd
import dash_table
from dash.dependencies import Input, Output, State
import plotly.express as px
from dash import callback_context, no_update
import webbrowser
from flask import request
chrome_path = 'C:/Program Files (x86)/Google/Chrome/Application/chrome.exe %s'
df = pd.read_csv("https://raw.githubusercontent.com/plotly/datasets/master/Mining-BTC-180.csv").drop('Unnamed: 0',axis=1)
selected_cols = ['Number-transactions', 'Output-volume(BTC)',
'Market-price', 'Hash-rate', 'Cost-per-trans-USD', 'Mining-revenue-USD',
'Transaction-fees-BTC']
class Parameter_Viewer():
def __init__(self, df, **kwargs):
revenue_plot = px.scatter(df, x="Date", y="Mining-revenue-USD",title='Mining-revenue-USD')
Hash_plot = px.scatter(df, x="Date", y="Hash-rate",title='Hash-rate')
parameter_table = dash_table.DataTable(
id='db-table',
columns=[{"name": i, "id": i} for i in selected_cols],
data=df.to_dict('records'),
#page_action="native",
page_action='none',
style_table={'height': '300px', 'overflowY': 'auto'},
editable=True
)
app = dash.Dash()
app.layout = html.Div(
children=[
#first row
html.Div(
children=[
html.Div(children=[
html.H1("Parameter", className="menu-title"),
dcc.Dropdown(
id="parameter-filter",
options=[
{"label": parameter, "value": parameter} for parameter in df.columns
],
value="Mining-revenue-USD",
clearable=False,
className="dropdown",
),]),
html.Div(children=[
html.H1("Data Type", className="menu-title"),
dcc.Dropdown(
id="data-selector",
options=[
{"label": data_col, "value": data_col} for data_col in selected_cols
],
value="Hash-rate",
clearable=False,
className="dropdown",
),]),
html.Button("Close Viewer", id="Close_Btn")
]
),
html.Div(children=[
html.H1(children="Database Analytics",),
parameter_table]),
html.Div(children=[
html.Div(
dcc.Graph(id='param-plot',figure=revenue_plot),
style={"width":'50%', "margin": 0, 'display': 'inline-block'},className="six columns"),
html.Div(
dcc.Graph(id='param-plot2',figure=Hash_plot),
style={"width":'50%', "margin": 0, 'display': 'inline-block'},className="six columns")],className="row")
])
#app.callback(
[Output("db-table", "data"), Output("param-plot", "figure"), Output("param-plot2", "figure")],
[Input("parameter-filter", "value"),Input("data-selector", "value"),Input('db-table', 'data')])#,prevent_initial_call=True)
def update_charts(parameter,data_type,table_data):
changed_inputs = [x["prop_id"]for x in callback_context.triggered]
if changed_inputs[0] == 'parameter-filter.value':
df = pd.DataFrame.from_dict(table_data)
ua_plot = px.scatter(df, x="Date", y=data_type)
aa_plot = px.scatter(df, x="Date", y=parameter)
return table_data, ua_plot, aa_plot
elif changed_inputs[0] == 'db-table.data':
df = pd.DataFrame.from_dict(table_data)
ua_plot = px.scatter(df, x="Date", y=data_type)
aa_plot = px.scatter(df, x="Date", y=parameter)
return no_update, ua_plot, aa_plot
else:
return no_update,no_update,no_update
#app.callback(Output("db-table", "data"),Input("Close_Btn", "n_clicks"),prevent_initial_call=True)
def close_browser(n_clicks):
print('In close callback\n')
if n_clicks>0:
self.shutdown()
return no_update
host='127.0.0.1'
port='8050'
url = 'http://{}:{}/'.format(host,port)
webbrowser.get(chrome_path).open(url)
app.run_server(debug=False)
def shutdown(self):
func = request.environ.get('werkzeug.server.shutdown')
if func is None:
raise RuntimeError('Not running with the Werkzeug Server')
func()
Parameter_Viewer(df)```
It's easier to consider base cases of what your callbacks may look like and then extend upon them. From my experience, the main reason I typically structure a callback is to handle a particular output caused by a particular input.
E.g.
If output O1 is an effect of input I1, then I make a callback C1(I1 → O1).
If output O1 is an effect of Inputs I1 and I2, then I make a callback C1([I1, I2] → O1)
If you happen to have multiple outputs that are effects of an input, then you combine those outputs in the same callback.
E.g.
If output O1 is an effect of input I1 and output O2 is an effect of input I1, then I make a callback C1(I1 → [O1, O2])
Also, keep in mind that Dash doesn't allow multiple callbacks to have the same output component (e.g., a particular component property can be associated with one and only one callback). In which case, if you have a particular output that requires to be updated simultaneously with another, but is not necessarily an effect of that same input, then you should still combine the outputs into the single callback.
E.g.
If output O1 is an effect of input I1 and output O2 must be updated with O1, then I make a callback C1(I1 → [O1, O2])
I'm not sure why in the comment you tried using State() instead of Input(), as State() is used in the event a property must still be read as input, but doesn't necessarily have to be triggered.
So using the above logic, e.g.,
if output O1 that is an effect of input I1 and is not an effect of Input (which we now call State) S1, but still requires S1, then I make a callback C1([I1, S1] → [O1])
It's normal to have large callbacks that have many inputs and outputs as long as the callback is not trying to do too many disjointed things at once (otherwise when you go back to look at your code you'll spend a lot of time trying to remember what you did believe me :)).
If you're a beginner like me. You're probably looking for the most valuable information, in my case, in Daniel Al Mouiee's answer:
Dash doesn't allow multiple callbacks to have the same output component
Context
In a dashboard using plotly Dash I need to perform an expensive download from DB only when a component (DataPicker with the period to consider and so to be downloaded from DB) is updated and then use the resulting DataFrame with other components (e.g. Dropdowns filtering the DataFrame) avoiding the expensive download process.
The docs suggests to use dash_core_components.Store as Output of a callback that return the DataFrame serielized in json and than use the Store as Input of other callbacks that needs to deserialize from json to DataFrame.
Serialization from/to JSON is slow, and each time I update a component it takes 30 seconds to update the plot just for that.
I tried to use faster serializations like pickle, parquet and feather but in the deserialization part I get an error stating that the object is empty (when using JSON no such error appear).
Question
Is it possible to perform serializations in Dash Store with faster methods like pickle, feather or parquet (they takes approx half of time for my dataset) than JSON? How?
Code
import io
import traceback
import pandas as pd
from datetime import datetime, date, timedelta
import dash
import dash_core_components as dcc
import dash_html_components as html
import dash_bootstrap_components as dbc
from dash.dependencies import Input, Output
from plotly.subplots import make_subplots
app = dash.Dash(__name__, external_stylesheets=[dbc.themes.BOOTSTRAP])
today = date.today()
app.layout = html.Div([
dbc.Row(dbc.Col(html.H1('PMC'))),
dbc.Row(dbc.Col(html.H5('analysis'))),
html.Hr(),
html.Br(),
dbc.Container([
dbc.Row([
dbc.Col(
dcc.DatePickerRange(
id='date_ranges',
start_date=today - timedelta(days=20),
end_date=today,
max_date_allowed=today, display_format='MMM Do, YY',
),
width=4
),
]),
dbc.Row(
dbc.Col(
dcc.Dropdown(
id='dd_ycolnames',
options=options,
value=default_options,
multi=True,
),
),
),
]),
dbc.Row([
dbc.Col(
dcc.Graph(
id='graph_subplots',
figure={},
),
width=12
),
]),
dcc.Store(id='store')
])
#app.callback(
Output('store', 'data'),
[
Input(component_id='date_ranges', component_property='start_date'),
Input(component_id='date_ranges', component_property='end_date')
]
)
def load_dataset(date_ranges_start, date_ranges_end):
# some expensive clean data step
logger.info('loading dataset...')
date_ranges1_start = datetime.strptime(date_ranges_start, '%Y-%m-%d')
date_ranges1_end = datetime.strptime(date_ranges_end, '%Y-%m-%d')
df = expensive_load_from_db(date_ranges1_start, date_ranges1_end)
logger.info('dataset to json...')
#return df.to_json(date_format='iso', orient='split')
return df.to_parquet() # <----------------------
#app.callback(
Output(component_id='graph_subplots', component_property='figure'),
[
Input(component_id='store', component_property='data'),
Input(component_id='dd_ycolnames', component_property='value'),
],
)
def update_plot(df_bin, y_colnames):
logger.info('dataset from json')
#df = pd.read_json(df_bin, orient='split')
df = pd.read_parquet(io.BytesIO(df_bin)) # <----------------------
logger.info('building plot...')
traces = []
for y_colname in y_colnames:
if df[y_colname].dtype == 'bool':
df[y_colname] = df[y_colname].astype('int')
traces.append(
{'x': df['date'], 'y': df[y_colname].values, 'name': y_colname},
)
fig = make_subplots(
rows=len(y_colnames), cols=1, shared_xaxes=True, vertical_spacing=0.1
)
fig.layout.height = 1000
for i, trace in enumerate(traces):
fig.append_trace(trace, i+1, 1)
logger.info('plotted')
return fig
if __name__ == '__main__':
app.run_server(host='localhost', debug=True)
Error text
OSError: Could not open parquet input source '<Buffer>': Invalid: Parquet file size is 0 bytes
Due to the exchange of data between client and server, you are currently limited to JSON serialization. One way to circumvent this limitation is via the ServersideOutput component from dash-extensions, which stores the data on the server. It uses file storage and pickle serialization by default, but you can use other storage (e.g. Redis) and/or serialization protocols (e.g. arrow) as well. Here is a small example,
import time
import dash_core_components as dcc
import dash_html_components as html
import plotly.express as px
from dash_extensions.enrich import Dash, Output, Input, State, ServersideOutput
app = Dash(prevent_initial_callbacks=True)
app.layout = html.Div([
html.Button("Query data", id="btn"), dcc.Dropdown(id="dd"), dcc.Graph(id="graph"),
dcc.Loading(dcc.Store(id='store'), fullscreen=True, type="dot")
])
#app.callback(ServersideOutput("store", "data"), Input("btn", "n_clicks"))
def query_data(n_clicks):
time.sleep(1)
return px.data.gapminder() # no JSON serialization here
#app.callback(Input("store", "data"), Output("dd", "options"))
def update_dd(df):
return [{"label": column, "value": column} for column in df["year"]] # no JSON de-serialization here
#app.callback(Output("graph", "figure"), [Input("dd", "value"), State("store", "data")])
def update_graph(value, df):
df = df.query("year == {}".format(value)) # no JSON de-serialization here
return px.sunburst(df, path=['continent', 'country'], values='pop', color='lifeExp', hover_data=['iso_alpha'])
if __name__ == '__main__':
app.run_server()
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.