Dash callback preventing other callbacks

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)```



Solution 1:

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 :)).

Solution 2:

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