Invalid Callback - Figure Works with fig.show() but not as dcc.Graph

Hi Everyone,

First time poster here so please excuse any rookie mistakes.

I’m creating a Dash App which is essentially a Monte Carlo simulation with a few additional criteria to define the parameters of the simulation.

I’ve created the exact same code both as part of the Dash App, and standalone without the app framework. The code runs perfectly when I use the standalone version, but when I use the App version, I get a callback error

dash.exceptions.InvalidCallbackReturnValue: The callback for [<Output br_graph.figure>, <Output mean-br-end.children>, <Output min-br-end.children>, <Output max-br-end.children>, <Output mode-end-stake.children>, <Output min-end-stake.children>, <Output max-end-stake.children>]
returned a value having type Figure
which is not JSON serializable.

The value in question is either the only value returned,
or is in the top level of the returned list, and has string representation

My callback looks like this:


@br_app.callback(
    Output(component_id = 'br_graph', component_property = 'figure'),
    Output(component_id = 'mean-br-end', component_property = 'children'),
    Output(component_id = 'min-br-end', component_property = 'children'),
    Output(component_id = 'max-br-end', component_property = 'children'),
    Output(component_id = 'mode-end-stake', component_property = 'children'),
    Output(component_id = 'min-end-stake', component_property = 'children'),
    Output(component_id = 'max-end-stake', component_property = 'children'),
    inputs = [Input('br-button', 'n_clicks')],
    state = [State('winrate-input', 'value'),
             State('std-input', 'value'),
             State('hands-input', 'value'),
             State('starting-br-input', 'value'),
             State('move-up-input', 'value'),
             State('move-down-input', 'value')])



def update_br_graph(n_clicks, wr, std, hands, br, move_up, move_down_input):

    #Create simulated BB winnings
    list_wrperhand = float(wr/100)
    list_stdperhand = float(std/10)
    list_hands = int(hands)


    #Create list of simulations
    sims_list = ['sim' +" " + str(x) for x in range(1,21)]

    #populate each simulation with results for each hand
    for i in range(0,20):
        sims_list[i] = np.random.normal(list_wrperhand, list_stdperhand, list_hands)
        sims_list[i][0] = 0



    #cumulate results for each hand for overall results in BBs
    sims_list_cum = [np.cumsum(x) for x in sims_list]



    #Pass to dictionary to give each sim a label
    names = ['sim ' + str(x) for x in range(1,21)]


    sim_dict = {}
    for i in range(0,20):
        sim_dict['sim '+str(i+1)] = sims_list[i]

    xaxis = ()
    sim_graph_test = []
    if hands > 100000:
        sim_graph_test = [x[0:List_hands-1:10] for x in sims_list_cum]
        xaxis = np.arange(0, list_hands/10)
    else:
        sim_graph_test = [x[:] for x in sims_list_cum]
        xaxis = np.arange(0, list_hands)

    starting_br = float(br)
    move_down = float(move_down_input)

    stakes = [2, 5, 10, 25, 50, 100, 200, 500, 1000, 2000]


    #Iterate through simulated winnings
    def Check_Stakes(br, move_down):#, move_up, move_down):
            if br <= move_down*stakes[9]:
                current_stake = stakes[8]
            if move_down*stakes[7]< br <= move_down*stakes[8]:
                current_stake = stakes[7]
            if move_down * stakes[6] < br  <= move_down*stakes[7]:
                current_stake = stakes[6]
            if move_down * stakes[5] < br <= move_down * stakes[6]:
                current_stake = stakes[5]
            if move_down*stakes[4] < br <= move_down * stakes[5]:
                current_stake = stakes[4]
            if move_down*stakes[3] < br <= move_down * stakes[4]:
                current_stake = stakes[3]
            if move_down*stakes[2] < br <= move_down * stakes[3]:
                current_stake = stakes[2]
            if move_down*stakes[1] < br <= move_down * stakes[2]:
                current_stake = stakes[1]
            if 0 < br <= move_down * stakes[1]:
                current_stake = stakes[0]
            elif move_down*stakes[7] <= br:
                current_stake = stakes[7]
            return current_stake


    bank_roll_dict = {}
    def Iterate(bank_roll_dict):
        for sim in names:
            bank_roll_dict[sim][0] = starting_br
            for hand in range(1, list_hands):
                stake = Check_Stakes(bank_roll_dict[sim][hand-1], move_down)#, move_up, move_down)
                if bank_roll_dict[sim][hand-1] <= 0:
                    bank_roll_dict[sim][hand] = 0
                else:
                    bank_roll_dict[sim][hand] = bank_roll_dict[sim][hand]*(stake/100) + bank_roll_dict[sim][hand-1]

        return bank_roll_dict


    
    bank_roll_dict =Iterate(sim_dict)
    bank_roll_list = [x for x in bank_roll_dict.values()]
    bank_roll_df = pd.DataFrame(bank_roll_dict)
   

    #Table Data
    end_br = bank_roll_df.iloc[-1, :]
    end_br = pd.DataFrame(end_br)

    mean_end_br = round(end_br.mean(), 2)
    min_end_br = round(end_br.min(), 2)
    max_end_br = round(end_br.max(), 2)

    #Determine End Stakes
    end_stake_list = [Check_Stakes(x, move_down) for x in end_br]
    end_stake = pd.DataFrame(end_stake_list)
    end_stake.columns = ["End Stake"]
    end_stake.reindex(names)
    mode_end_stake = end_stake.mode()
    max_end_stake = end_stake.max()
    min_end_stake = end_stake.min()


    br_graph = px.line(x = xaxis, y = bank_roll_list)


    br_graph.update_layout(yaxis_title = 'BR Amount ($)')
    if hands > 1000000:
        br_graph.update_traces(hovertemplate = 'Hand %{x*10} <br> BR Amount $%{y}')
    else:
        br_graph.update_traces(hovertemplate = 'Hand %{x} <br> BR Amount $%{y}')




    return br_graph, mean_end_br, min_end_br, max_end_br, mode_end_stake, min_end_stake, max_end_stake
br_app.run_server(debug = True, port = '8000')

Any advice would be appreciated!

Hi @danself
Welcome to the community. And thanks for the question. I am on my phone so I might be misreading something but is your figure complete or did you forget to add a dataframe to the px.line chart?

You should wrap your fig into a dcc function. You just need to try print(type(br_graph)) to see why.

Because you didn’t give a reproducible example, I’m not sure if the following settings will help you bypass this check, since the Figure object also is essentially serialized.

app.config.suppress_callback_exceptions = True

or maybe you can just return str(br_graph)

I didn’t add a dataframe argument as I’m trying avoid using DF’s as much as possible - the users of the app may want to generate 000,000’s of data points, and DF’s slow down the app too much. All of the data is contained in two separate list of lists which is input directly as x and y data.

I have the same inputs in to a px.line function not inside a callback which works perfectly when using fig.show()

Thanks, the output is sent to a dcc.Graph object. I didn’t include the app layout as I don’t believe that’s the source of the problem, but here it is below.

import pandas as pd
import numpy as np
import scipy.stats as stats
import matplotlib.pyplot as plt


import plotly.express as px


import dash
from dash import dcc
from dash import dash_table
from dash import html
from dash.dependencies import Input, Output, State

br_app = dash.Dash(__name__)
br_app.layout = html.Div(children = [
                                html.H3('Results in BR ($)'),
                                html.Span(children = [
                                    html.I('Enter your starting BR, and number of buy ins required to move up or down'
                                          ' stakes. The next graph will show the progress of your BR.')
                                ]),
                            html.Div(children = [
                                html.Span(children = [html.B('Winrate (bb/100) ')]),
                                dcc.Input(
                                    id = "winrate-input",
                                    placeholder = 'Enter Winrate (bb/100)',
                                    type = 'number',
                                    value = 5),
                                html.Span(children = [html.B('STD (bb/100) ')]),
                                dcc.Input(
                                    id = "std-input",
                                    placeholder = 'Enter Standard Deviation (bb/100)',
                                    type = 'number',
                                    value = 100),
                                html.Span(children = [html.B('No. of Hands')]),
                                dcc.Input(
                                    id = "hands-input",
                                    placeholder = 'Enter No. Hands to Simulate',
                                    type = 'number',
                                    value = 10000)]),
                            html.Div(children = [
                                html.Span(children = [html.B('Starting BR ($) ')]),
                                dcc.Input(
                                    id = "starting-br-input",
                                    placeholder = 'Enter Starting BR ($)',
                                    type = 'number',
                                    value = 1000),
                                html.Span(children = [html.B(' BIs to Move Up ')]),
                                dcc.Input(
                                    id = "move-up-input",
                                    placeholder = 'Enter Buy Ins to Move Up',
                                    type = 'number',
                                    value = 40),
                                html.Span(children = [html.B(' BIs to Move Down ')]),
                                dcc.Input(
                                    id = "move-down-input",
                                    placeholder = 'Enter Buy Ins to Move Down',
                                    type = 'number',
                                    value = 30)]),
                                html.Button(id='br-button', n_clicks=0, children='Submit'),

                                html.Div(dcc.Graph(id = 'br_graph', style = {'width':'50%'})),
                                html.Table([
                                    html.Tr([html.Td(['Mean End BR ($)']), html.Td(id = 'mean-br-end')]),
                                    html.Tr([html.Td(['Minimum End BR ($)']), html.Td(id = 'min-br-end')]),
                                    html.Tr([html.Td(['Maximum End BR ($)']), html.Td(id = 'max-br-end')]),
                                    html.Tr([html.Td(['Most Common Ending Stake']), html.Td(id = 'mode-end-stake')]),
                                    html.Tr([html.Td(['Lowest Ending Stake']), html.Td(id = 'min-end-stake')]),
                                    html.Tr([html.Td(['Highest Ending Stake']), html.Td(id = 'max-end-stake')]),
                                ])])

Oh, dude, too complex, I can’t run your code successfully, or just that my raspi can’t afford it.
I’ve tried this:

import pandas as pd
import numpy as np
import scipy.stats as stats
import matplotlib.pyplot as plt

import plotly.express as px

import dash
from dash import dcc
from dash import dash_table
from dash import html
from dash.dependencies import Input, Output, State

df = pd.DataFrame({
    "Fruit": ["Apples", "Oranges", "Bananas", "Apples", "Oranges", "Bananas"],
    "Amount": [4, 1, 2, 2, 4, 5],
    "City": ["SF", "SF", "SF", "Montreal", "Montreal", "Montreal"]
})

fig = px.bar(df, x="Fruit", y="Amount", color="City", barmode="group")

app = dash.Dash(__name__)
app.layout = html.Div(children=[
    html.H3('Results in BR ($)'),
    html.Span(children=[
        html.
        I('Enter your starting BR, and number of buy ins required to move up or down'
          ' stakes. The next graph will show the progress of your BR.')
    ]),
    html.Div(children=[
        html.Span(children=[html.B('Winrate (bb/100) ')]),
        dcc.Input(id="winrate-input",
                  placeholder='Enter Winrate (bb/100)',
                  type='number',
                  value=5),
        html.Span(children=[html.B('STD (bb/100) ')]),
        dcc.Input(id="std-input",
                  placeholder='Enter Standard Deviation (bb/100)',
                  type='number',
                  value=100),
        html.Span(children=[html.B('No. of Hands')]),
        dcc.Input(id="hands-input",
                  placeholder='Enter No. Hands to Simulate',
                  type='number',
                  value=10000)
    ]),
    html.Div(children=[
        html.Span(children=[html.B('Starting BR ($) ')]),
        dcc.Input(id="starting-br-input",
                  placeholder='Enter Starting BR ($)',
                  type='number',
                  value=1000),
        html.Span(children=[html.B(' BIs to Move Up ')]),
        dcc.Input(id="move-up-input",
                  placeholder='Enter Buy Ins to Move Up',
                  type='number',
                  value=40),
        html.Span(children=[html.B(' BIs to Move Down ')]),
        dcc.Input(id="move-down-input",
                  placeholder='Enter Buy Ins to Move Down',
                  type='number',
                  value=30)
    ]),
    html.Button(id='br-button', n_clicks=0, children='Submit'),
    html.Div(dcc.Graph(id='br_graph', style={'width': '50%'})),
    html.Table([
        html.Tr([html.Td(['Mean End BR ($)']),
                 html.Td(id='mean-br-end')]),
        html.Tr([html.Td(['Minimum End BR ($)']),
                 html.Td(id='min-br-end')]),
        html.Tr([html.Td(['Maximum End BR ($)']),
                 html.Td(id='max-br-end')]),
        html.Tr([
            html.Td(['Most Common Ending Stake']),
            html.Td(id='mode-end-stake')
        ]),
        html.Tr(
            [html.Td(['Lowest Ending Stake']),
             html.Td(id='min-end-stake')]),
        html.Tr(
            [html.Td(['Highest Ending Stake']),
             html.Td(id='max-end-stake')]),
    ])
])


@app.callback(Output(component_id='br_graph', component_property='figure'),
              inputs=[Input('br-button', 'n_clicks')],
              state=[
                  State('winrate-input', 'value'),
                  State('std-input', 'value'),
                  State('hands-input', 'value'),
                  State('starting-br-input', 'value'),
                  State('move-up-input', 'value'),
                  State('move-down-input', 'value')
              ])
def update_br_graph(n_clicks, wr, std, hands, br, move_up, move_down_input):

    #Create simulated BB winnings
    return fig


print(type(fig))
print(fig)

if __name__ == "__main__":
    app.run_server(debug=True, port='8000')

it works.

And maybe you need to spend a little time to understand how callbacks work. Check the docs.

I don’t know exactly the purpose of your series of calculations, but I think some of the steps can be pre-calculated. I mean, don’t have to put them all in the callback.

Thanks, I’ve been through the docs multiple times.

I know how to get it working in the callback, but it’s only if I do the same as what you have done - but this defeats the purpose as the figure object will be opened in a separate tab to the app.

Unfortunately all the steps need to be in the callback as they all derive from the inputs of the user.

I appreciate your time and effort.

import pandas as pd
import numpy as np
import scipy.stats as stats
import matplotlib.pyplot as plt

import plotly.express as px

import dash
from dash import dcc
from dash import dash_table
from dash import html
from dash.dependencies import Input, Output, State

df = pd.DataFrame({
    "Fruit": ["Apples", "Oranges", "Bananas", "Apples", "Oranges", "Bananas"],
    "Amount": [4, 1, 2, 2, 4, 5],
    "City": ["SF", "SF", "SF", "Montreal", "Montreal", "Montreal"]
})

fig = px.bar(df, x="Fruit", y="Amount", color="City", barmode="group")

app = dash.Dash(__name__)
app.layout = html.Div(children=[
    html.H3('Results in BR ($)'),
    html.Span(children=[
        html.
        I('Enter your starting BR, and number of buy ins required to move up or down'
          ' stakes. The next graph will show the progress of your BR.')
    ]),
    html.Div(children=[
        html.Span(children=[html.B('Winrate (bb/100) ')]),
        dcc.Input(id="winrate-input",
                  placeholder='Enter Winrate (bb/100)',
                  type='number',
                  value=5),
        html.Span(children=[html.B('STD (bb/100) ')]),
        dcc.Input(id="std-input",
                  placeholder='Enter Standard Deviation (bb/100)',
                  type='number',
                  value=100),
        html.Span(children=[html.B('No. of Hands')]),
        dcc.Input(id="hands-input",
                  placeholder='Enter No. Hands to Simulate',
                  type='number',
                  value=10000)
    ]),
    html.Div(children=[
        html.Span(children=[html.B('Starting BR ($) ')]),
        dcc.Input(id="starting-br-input",
                  placeholder='Enter Starting BR ($)',
                  type='number',
                  value=1000),
        html.Span(children=[html.B(' BIs to Move Up ')]),
        dcc.Input(id="move-up-input",
                  placeholder='Enter Buy Ins to Move Up',
                  type='number',
                  value=40),
        html.Span(children=[html.B(' BIs to Move Down ')]),
        dcc.Input(id="move-down-input",
                  placeholder='Enter Buy Ins to Move Down',
                  type='number',
                  value=30)
    ]),
    html.Button(id='br-button', n_clicks=0, children='Submit'),
    html.Div(dcc.Graph(id='br_graph', style={'width': '50%'})),
    html.Table([
        html.Tr([html.Td(['Mean End BR ($)']),
                 html.Td(id='mean-br-end')]),
        html.Tr([html.Td(['Minimum End BR ($)']),
                 html.Td(id='min-br-end')]),
        html.Tr([html.Td(['Maximum End BR ($)']),
                 html.Td(id='max-br-end')]),
        html.Tr([
            html.Td(['Most Common Ending Stake']),
            html.Td(id='mode-end-stake')
        ]),
        html.Tr(
            [html.Td(['Lowest Ending Stake']),
             html.Td(id='min-end-stake')]),
        html.Tr(
            [html.Td(['Highest Ending Stake']),
             html.Td(id='max-end-stake')]),
    ])
])

stakes = [2, 5, 10, 25, 50, 100, 200, 500, 1000, 2000]


#Iterate through simulated winnings
def Check_Stakes(br, move_down):  #, move_up, move_down):
    if br <= move_down * stakes[9]:
        current_stake = stakes[8]
    if move_down * stakes[7] < br <= move_down * stakes[8]:
        current_stake = stakes[7]
    if move_down * stakes[6] < br <= move_down * stakes[7]:
        current_stake = stakes[6]
    if move_down * stakes[5] < br <= move_down * stakes[6]:
        current_stake = stakes[5]
    if move_down * stakes[4] < br <= move_down * stakes[5]:
        current_stake = stakes[4]
    if move_down * stakes[3] < br <= move_down * stakes[4]:
        current_stake = stakes[3]
    if move_down * stakes[2] < br <= move_down * stakes[3]:
        current_stake = stakes[2]
    if move_down * stakes[1] < br <= move_down * stakes[2]:
        current_stake = stakes[1]
    if 0 < br <= move_down * stakes[1]:
        current_stake = stakes[0]
    elif move_down * stakes[7] <= br:
        current_stake = stakes[7]
    return current_stake


def Iterate(bank_roll_dict, names, list_hands, move_down, starting_br):
    for sim in names:
        bank_roll_dict[sim][0] = starting_br
        for hand in range(1, list_hands):
            stake = Check_Stakes(bank_roll_dict[sim][hand - 1],
                                 move_down)  #, move_up, move_down)
            if bank_roll_dict[sim][hand - 1] <= 0:
                bank_roll_dict[sim][hand] = 0
            else:
                bank_roll_dict[sim][hand] = bank_roll_dict[sim][hand] * (
                    stake / 100) + bank_roll_dict[sim][hand - 1]

    return bank_roll_dict


@app.callback(Output(component_id='br_graph', component_property='figure'),
              Output(component_id='mean-br-end',
                     component_property='children'),
              Output(component_id='min-br-end', component_property='children'),
              Output(component_id='max-br-end', component_property='children'),
              Output(component_id='mode-end-stake',
                     component_property='children'),
              Output(component_id='min-end-stake',
                     component_property='children'),
              Output(component_id='max-end-stake',
                     component_property='children'),
              inputs=[Input('br-button', 'n_clicks')],
              state=[
                  State('winrate-input', 'value'),
                  State('std-input', 'value'),
                  State('hands-input', 'value'),
                  State('starting-br-input', 'value'),
                  State('move-up-input', 'value'),
                  State('move-down-input', 'value')
              ])
def update_br_graph(n_clicks, wr, std, hands, br, move_up, move_down_input):

    #Create simulated BB winnings
    list_wrperhand = float(wr / 100)
    list_stdperhand = float(std / 10)
    list_hands = int(hands)

    #Create list of simulations
    sims_list = ['sim' + " " + str(x) for x in range(1, 21)]

    #populate each simulation with results for each hand
    for i in range(0, 20):
        sims_list[i] = np.random.normal(list_wrperhand, list_stdperhand,
                                        list_hands)
        sims_list[i][0] = 0

    #cumulate results for each hand for overall results in BBs
    sims_list_cum = [np.cumsum(x) for x in sims_list]

    #Pass to dictionary to give each sim a label
    names = ['sim ' + str(x) for x in range(1, 21)]

    sim_dict = {}
    for i in range(0, 20):
        sim_dict['sim ' + str(i + 1)] = sims_list[i]

    xaxis = ()
    sim_graph_test = []
    if hands > 100000:
        sim_graph_test = [x[0:list_hands - 1:10] for x in sims_list_cum]
        xaxis = np.arange(0, list_hands / 10)
    else:
        sim_graph_test = [x[:] for x in sims_list_cum]
        xaxis = np.arange(0, list_hands)

    starting_br = float(br)
    move_down = float(move_down_input)

    bank_roll_dict = {}

    bank_roll_dict = Iterate(sim_dict, names, list_hands, move_down,
                             starting_br)
    bank_roll_list = [x for x in bank_roll_dict.values()]
    bank_roll_df = pd.DataFrame(bank_roll_dict)

    #Table Data
    end_br = bank_roll_df.iloc[-1, :]
    end_br = pd.DataFrame(end_br)

    mean_end_br = round(end_br.mean(), 2)
    min_end_br = round(end_br.min(), 2)
    max_end_br = round(end_br.max(), 2)

    #Determine End Stakes
    end_stake_list = [Check_Stakes(x, move_down) for x in end_br]
    end_stake = pd.Series(end_stake_list)
    #end_stake.columns = ["End Stake"]
    #end_stake.reindex(names)
    mode_end_stake = end_stake.mode()[0]
    max_end_stake = end_stake.max()
    min_end_stake = end_stake.min()

    br_graph = px.line(x=xaxis, y=bank_roll_list)

    br_graph.update_layout(yaxis_title='BR Amount ($)')
    if hands > 1000000:
        br_graph.update_traces(
            hovertemplate='Hand %{x*10} <br> BR Amount $%{y}')
    else:
        br_graph.update_traces(hovertemplate='Hand %{x} <br> BR Amount $%{y}')

    return br_graph, mean_end_br, min_end_br, max_end_br, mode_end_stake, min_end_stake, max_end_stake


#app.run_server(debug = True, port = '8000')

if __name__ == "__main__":
    app.run_server(debug=True, port='8000')

Now it works.
Maybe you also need to learn how to use pandas.

Parameters data ndarray (structured or homogeneous), Iterable, dict, or DataFrame

1 Like

@adamschroeder I don’t know why Dash is warning about a Figure type, that’s actually a None type.

they are returning lists to the children instead of strings

Hi @danself

Your graph is ok. You’re getting this error because the rest of the objects you are returning are lists. And lists cannot be returned in the way that you have html.Td. set up.

Try doing this:

    return br_graph, mean_end_br.values[0], min_end_br.values[0], max_end_br.values[0], mode_end_stake.values[0], min_end_stake.values[0], max_end_stake.values[0]
1 Like