Multiple functions in Plotly Dash app call back?
Question:
I’m creating a fitness chart using Plotly Dash that allows a user to enter a weight, which saves the data to an excel file, and then the user can refresh the screen to update the graph. I’ve been able to do them seperately by only having one function under the app.callback section. How can I have both functions? I can make the graph OR I can collect the input and refresh, but not both. Here’s a sample of the data I’m using.
And here’s the MVP code I’m trying to use.
import openpyxl
import dash
from dash import html, dcc, Input, Output, State
import pandas as pd
import plotly.express as px
import plotly.graph_objects as go
app = dash.Dash(__name__)
app.layout = html.Div([
dcc.Input(id='weight', placeholder='Enter Your Weight', type='text'),
html.Button(id='submit-button', type='submit', children='Submit'),
html.A(html.Button('Refresh'), href='/'),
dcc.Graph(
id='chart')
])
@app.callback(Output('chart', 'figure'),
[Input('submit-button', 'n_clicks')],
[State('weight', 'value')],
)
def display_time_series(n_clicks, input_value):
xlsx = pd.read_excel('Weight Tracker.xlsx')
df = xlsx
fig = px.line(df, x="DATE", y="ACTUAL WEIGHT")
fig.add_trace(
go.Scatter(x=df['DATE'], y=df['HIGH ESTIMATE'], name="HIGH ESTIMATE", line=dict(color="green", dash="dash")),
secondary_y=False,
)
fig.add_trace(
go.Scatter(x=df['DATE'], y=df['LOW ESTIMATE'], name="LOW ESTIMATE", line=dict(color="red", dash="dash")),
secondary_y=False,
)
if n_clicks is not None:
wb = openpyxl.load_workbook('Weight Tracker.xlsx')
sheet = wb.active
# Finds the last open row in Column B or the 'Actual Weight' Column
last_empty_entry = max((b.row for b in sheet['B'] if b.value is not None)) + 1
c1 = sheet.cell(row=last_empty_entry, column=2)
c1.value = int(input_value)
wb.save("Weight Tracker.xlsx")
print("Excel has been saved.")
return fig
if __name__ == '__main__':
app.run_server(debug=True)
Here’s the error I’m getting and the graph doesn’t display and the input button doesn’t do anything.
Cannot read properties of null (reading 'data')
at f.value (http://127.0.0.1:8050/_dash-component-suites/dash/dcc/async-graph.js:1:4493)
at f.value (http://127.0.0.1:8050/_dash-component-suites/dash/dcc/async-graph.js:1:9778)
at callComponentWillReceiveProps (http://127.0.0.1:8050/_dash-component-suites/dash/deps/[email protected]_3_1m1648990364.14.0.js:13111:16)
at updateClassInstance (http://127.0.0.1:8050/_dash-component-suites/dash/deps/[email protected]_3_1m1648990364.14.0.js:13313:9)
at updateClassComponent (http://127.0.0.1:8050/_dash-component-suites/dash/deps/[email protected]_3_1m1648990364.14.0.js:17242:22)
at beginWork (http://127.0.0.1:8050/_dash-component-suites/dash/deps/[email protected]_3_1m1648990364.14.0.js:18755:18)
at HTMLUnknownElement.callCallback (http://127.0.0.1:8050/_dash-component-suites/dash/deps/[email protected]_3_1m1648990364.14.0.js:182:16)
at Object.invokeGuardedCallbackDev (http://127.0.0.1:8050/_dash-component-suites/dash/deps/[email protected]_3_1m1648990364.14.0.js:231:18)
at invokeGuardedCallback (http://127.0.0.1:8050/_dash-component-suites/dash/deps/[email protected]_3_1m1648990364.14.0.js:286:33)
at beginWork$1 (http://127.0.0.1:8050/_dash-component-suites/dash/deps/[email protected]_3_1m1648990364.14.0.js:23338:9)
Answers:
The main issue you’re having is the callback is being called at initial start of program, so to fix this pass in prevent_initial_callbacks=True into dash app instance.
Then you need 2 separate inputs for each button and don’t use an anchor for Refresh button it won’t work.
import dash
from dash import html, dcc, Input, Output, State
import pandas as pd
import plotly.express as px
import plotly.graph_objects as go
import datetime as dt
app = dash.Dash(__name__, prevent_initial_callbacks=True)
app.layout = html.Div([
dcc.Input(id='weight', placeholder='Enter Your Weight', type='text'),
html.Button(id='submit-button', type='submit', children='Submit'),
html.Button('Refresh', id='refresh'),
dcc.Graph(id='chart'),
html.P(children='dummy', id='dummy', hidden=True)
])
@app.callback(Output('chart', 'figure'),
[Input('refresh', 'n_clicks')],
prevent_initial_callback=True,
)
def display_time_series(n_clicks):
if n_clicks is not None:
xlsx = pd.read_excel('Weight Tracker.xlsx')
df = xlsx
fig = px.line(df, x="DATE", y="ACTUAL WEIGHT")
fig.add_trace(
go.Scatter(x=df['DATE'], y=df['HIGH ESTIMATE'], name="HIGH ESTIMATE", line=dict(color="green", dash="dash")),
secondary_y=False,
)
fig.add_trace(
go.Scatter(x=df['DATE'], y=df['LOW ESTIMATE'], name="LOW ESTIMATE", line=dict(color="red", dash="dash")),
secondary_y=False,
)
return fig
@app.callback(Output('dummy', 'children'),
[Input('submit-button', 'n_clicks')],
[State('weight', 'value')],
prevent_initial_callback=True
)
def save_new_entry(n_clicks, input_value):
if n_clicks is not None:
wb = openpyxl.load_workbook('Weight Tracker.xlsx')
sheet = wb.active
# Finds the last open row in Column B or the 'Actual Weight' Column
last_empty_entry = max((b.row for b in sheet['B'] if b.value is not None)) + 1
c0 = sheet.cell(row=last_empty_entry, column=1)
c0.value = dt.datetime.now()
c1 = sheet.cell(row=last_empty_entry, column=2)
c1.value = int(input_value)
wb.save("Weight Tracker.xlsx")
print("Excel has been saved.")
if __name__ == '__main__':
app.run_server(debug=True)
I’m creating a fitness chart using Plotly Dash that allows a user to enter a weight, which saves the data to an excel file, and then the user can refresh the screen to update the graph. I’ve been able to do them seperately by only having one function under the app.callback section. How can I have both functions? I can make the graph OR I can collect the input and refresh, but not both. Here’s a sample of the data I’m using.
And here’s the MVP code I’m trying to use.
import openpyxl
import dash
from dash import html, dcc, Input, Output, State
import pandas as pd
import plotly.express as px
import plotly.graph_objects as go
app = dash.Dash(__name__)
app.layout = html.Div([
dcc.Input(id='weight', placeholder='Enter Your Weight', type='text'),
html.Button(id='submit-button', type='submit', children='Submit'),
html.A(html.Button('Refresh'), href='/'),
dcc.Graph(
id='chart')
])
@app.callback(Output('chart', 'figure'),
[Input('submit-button', 'n_clicks')],
[State('weight', 'value')],
)
def display_time_series(n_clicks, input_value):
xlsx = pd.read_excel('Weight Tracker.xlsx')
df = xlsx
fig = px.line(df, x="DATE", y="ACTUAL WEIGHT")
fig.add_trace(
go.Scatter(x=df['DATE'], y=df['HIGH ESTIMATE'], name="HIGH ESTIMATE", line=dict(color="green", dash="dash")),
secondary_y=False,
)
fig.add_trace(
go.Scatter(x=df['DATE'], y=df['LOW ESTIMATE'], name="LOW ESTIMATE", line=dict(color="red", dash="dash")),
secondary_y=False,
)
if n_clicks is not None:
wb = openpyxl.load_workbook('Weight Tracker.xlsx')
sheet = wb.active
# Finds the last open row in Column B or the 'Actual Weight' Column
last_empty_entry = max((b.row for b in sheet['B'] if b.value is not None)) + 1
c1 = sheet.cell(row=last_empty_entry, column=2)
c1.value = int(input_value)
wb.save("Weight Tracker.xlsx")
print("Excel has been saved.")
return fig
if __name__ == '__main__':
app.run_server(debug=True)
Here’s the error I’m getting and the graph doesn’t display and the input button doesn’t do anything.
Cannot read properties of null (reading 'data')
at f.value (http://127.0.0.1:8050/_dash-component-suites/dash/dcc/async-graph.js:1:4493)
at f.value (http://127.0.0.1:8050/_dash-component-suites/dash/dcc/async-graph.js:1:9778)
at callComponentWillReceiveProps (http://127.0.0.1:8050/_dash-component-suites/dash/deps/[email protected]_3_1m1648990364.14.0.js:13111:16)
at updateClassInstance (http://127.0.0.1:8050/_dash-component-suites/dash/deps/[email protected]_3_1m1648990364.14.0.js:13313:9)
at updateClassComponent (http://127.0.0.1:8050/_dash-component-suites/dash/deps/[email protected]_3_1m1648990364.14.0.js:17242:22)
at beginWork (http://127.0.0.1:8050/_dash-component-suites/dash/deps/[email protected]_3_1m1648990364.14.0.js:18755:18)
at HTMLUnknownElement.callCallback (http://127.0.0.1:8050/_dash-component-suites/dash/deps/[email protected]_3_1m1648990364.14.0.js:182:16)
at Object.invokeGuardedCallbackDev (http://127.0.0.1:8050/_dash-component-suites/dash/deps/[email protected]_3_1m1648990364.14.0.js:231:18)
at invokeGuardedCallback (http://127.0.0.1:8050/_dash-component-suites/dash/deps/[email protected]_3_1m1648990364.14.0.js:286:33)
at beginWork$1 (http://127.0.0.1:8050/_dash-component-suites/dash/deps/[email protected]_3_1m1648990364.14.0.js:23338:9)
The main issue you’re having is the callback is being called at initial start of program, so to fix this pass in prevent_initial_callbacks=True into dash app instance.
Then you need 2 separate inputs for each button and don’t use an anchor for Refresh button it won’t work.
import dash
from dash import html, dcc, Input, Output, State
import pandas as pd
import plotly.express as px
import plotly.graph_objects as go
import datetime as dt
app = dash.Dash(__name__, prevent_initial_callbacks=True)
app.layout = html.Div([
dcc.Input(id='weight', placeholder='Enter Your Weight', type='text'),
html.Button(id='submit-button', type='submit', children='Submit'),
html.Button('Refresh', id='refresh'),
dcc.Graph(id='chart'),
html.P(children='dummy', id='dummy', hidden=True)
])
@app.callback(Output('chart', 'figure'),
[Input('refresh', 'n_clicks')],
prevent_initial_callback=True,
)
def display_time_series(n_clicks):
if n_clicks is not None:
xlsx = pd.read_excel('Weight Tracker.xlsx')
df = xlsx
fig = px.line(df, x="DATE", y="ACTUAL WEIGHT")
fig.add_trace(
go.Scatter(x=df['DATE'], y=df['HIGH ESTIMATE'], name="HIGH ESTIMATE", line=dict(color="green", dash="dash")),
secondary_y=False,
)
fig.add_trace(
go.Scatter(x=df['DATE'], y=df['LOW ESTIMATE'], name="LOW ESTIMATE", line=dict(color="red", dash="dash")),
secondary_y=False,
)
return fig
@app.callback(Output('dummy', 'children'),
[Input('submit-button', 'n_clicks')],
[State('weight', 'value')],
prevent_initial_callback=True
)
def save_new_entry(n_clicks, input_value):
if n_clicks is not None:
wb = openpyxl.load_workbook('Weight Tracker.xlsx')
sheet = wb.active
# Finds the last open row in Column B or the 'Actual Weight' Column
last_empty_entry = max((b.row for b in sheet['B'] if b.value is not None)) + 1
c0 = sheet.cell(row=last_empty_entry, column=1)
c0.value = dt.datetime.now()
c1 = sheet.cell(row=last_empty_entry, column=2)
c1.value = int(input_value)
wb.save("Weight Tracker.xlsx")
print("Excel has been saved.")
if __name__ == '__main__':
app.run_server(debug=True)