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.

enter image description here

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)
Asked By: Brandon Jacobson

||

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)

Answered By: hknjj
Categories: questions Tags: ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.