Trigger a calculation using a button and a datatable

Hello. I use data_table with 26 rows of user inputs as inputs to a keras model. The table is editable, and is pre-populated using suggested values from to ease the pain of data entry.

Unfortunately, the data_table callback is triggered and the model is run every time the user inputs a new value in the table. I tried using a button as a trigger, but every entry on the datatable initiates a callback no matter what I do. The button callback trigger works too, but overall, my code can’t distinguish between a button trigger and a new data point trigger in the data_table.

Is there a way to ignore the datatable callback, or a different way to load data into a dash that doesn’t require data_table?

Karl

Here is my code (well part of it, any way)


external_stylesheets = ['https://codepen.io/chriddyp/pen/bWLwgP.css']

app = Dash(__name__, external_stylesheets=external_stylesheets)

app.layout = html.Div([
    html.H1('LGR Predictor'),
    dcc.Tabs(id="lgr_tabs", value='tab-1', children=[
        dcc.Tab(label='Tab One', value='tab-1', children=[
  
   dash_table.DataTable(
        id='input_table',
        columns=[
            {"name": ["Feature"], "id": "feature"},
            {"name": ["Rank"], "id": "rank", "editable": False },
            {"name": ["Value"], "id": "value", "editable": True},
        ],
        data = [
                {'feature':'PR Avg_imp','value': 0.24, 'rank': 1}, 
                {'feature':'YM Avg_imp', 'value': 61, 'rank': 9}, 
                {'feature':'API_imp', 'value': 45, 'rank': 3}]
    ),
                    html.Div([
        html.Button(id='submit_button', n_clicks=0, children='Submit'),
        html.Div(id='output_state'),
    ],style={'text-align': 'center'}),
        ]),
        dcc.Tab(label='Tab Two', value='tab-2'),
    ]),
    html.Div(id='model_graphs') # this is the the content for the tab as a child
])

@app.callback(Output('model_graphs', 'children'), # the output content for the tab as a child
              [Input('submit_button', 'n_clicks')],
              Input('lgr_tabs', 'value'), #keeps track of which tab is active
              Input('input_table', 'data'), # the data from the input table
              State=[State('input_table', 'value')],
              )
def render_content(n_clicks, tab, rows, state):
    if tab == 'tab-1':
        print(rows)
        figure0 = html.Div([
            html.H3('Predictor Inputs'),
            dcc.Graph(
                figure={
                    'data': [{
                        'x': [1, 2, 3],
                        'y': [3, 1, 2],
                        'type': 'bar'
                    }]
                }
            ),

        ])
        return figure0
        

    elif tab == 'tab-2':
        figure0 = html.Div([
            html.H3('Tab content 2'),
            dcc.Graph(
                id='graph-2-tabs-dcc',
                figure={
                    'data': [{
                        'x': [1, 2, 3],
                        'y': [5, 10, 6],
                        'type': 'bar'
                    }]
                }
            )
        ])
        return figure0


if __name__ == '__main__':
    app.run_server(debug=False)

Hi @knorrena and welcome to the dash community :slight_smile:

If you use State rather than Input it will not trigger the callback. So you could try changing:

 Input('input_table', 'data')

to:

 State('input_table', 'data')

For the sake of posterity, and informing others, I got it to work. The trick was to track the name of the callback event. I rewrote a bit of code for more efficient testing shown below. The little snippet that did the trick is this:

dash.callback_context.triggered] generates a list that characterizes the callback trigger like so:
[{‘prop_id’: ‘submit.n_clicks’, ‘value’: 4}]

Parsing out the list gives the name of the event that caused the trigger. Once you have that, its a simple if then lets you discriminate between events. The code below is a simple and crude example.

#%%
import dash
from dash.dependencies import Input, Output, State
import dash_html_components as html
import dash_core_components as dcc
from dash import dash_table
from dash.exceptions import PreventUpdate


app = dash.Dash()

app.layout = html.Div([
    html.Button('Click Me', id='button'),
    html.H3(id='button-clicks'),

    html.Hr(),
        dash_table.DataTable(
            id='input_table',
            columns=[
                {"name": ["Feature"], "id": "feature"},
                {"name": ["Rank"], "id": "rank", "editable": False },
                {"name": ["Value"], "id": "value", "editable": True},
            ],
            data = [
                {'feature':'PR Avg_imp','value': 0.24, 'rank': 1}, 
                {'feature':'YM Avg_imp', 'value': 61, 'rank': 9}, 
                {'feature':'API_imp', 'value': 45, 'rank': 3}
            ]
        ),

    html.Label('Input 1'),
    dcc.Input(id='input-1'),

    html.Label('Input 2'),
    dcc.Input(id='input-2'),

    html.Label('Slider 1'),
    dcc.Input(id='slider-1'),

    html.Button('Submit', id='submit'),

    html.Div(id='output')
])

@app.callback(
    Output('button-clicks', 'children'),
    [Input('button', 'n_clicks')])
def clicks(n_clicks):
    if n_clicks is None:
        raise PreventUpdate
    else:
        n_clicks = None
        return 'Button has been clicked {} times'.format(n_clicks)

@app.callback(
    Output('output', 'children'),
    [Input('submit', 'n_clicks')],
    [Input('input_table', 'data')],
    [State('input-1', 'value'),
     State('input-2', 'value'),
     State('slider-1', 'value'),
     State('input_table', 'data_timestamp')])

def compute(n_clicks, rows, input1, input2, slider1, state):
    changed_id = [p['prop_id'].split('.')[0] for p in dash.callback_context.triggered]
    print('dash callback',dash.callback_context.triggered )
    print('change id',changed_id)
    print('type',type(changed_id))
    if changed_id[0] == 'submit':
        return 'A computation based off of {}, {}, and {}'.format(
        input1, input2, slider1 )
    else:
        print('change_loop')

if __name__ == '__main__':
    app.run_server(debug=False)
2 Likes

Hi @knorrena

Glad you got it working and for posting your solution! :rocket:

It looks like you are using a old version of dash. There is now a much easier way to find which input triggered a callback. You can find more information in the docs here: Determining Which Callback Input Changed | Dash for Python Documentation | Plotly

This is what your example would look like in dash==2.6.1


from dash import Dash, html, dcc, dash_table, ctx, Input, Output, State
from dash.exceptions import PreventUpdate

app = Dash(__name__)

app.layout = html.Div(
    [
        html.Button("Click Me", id="button"),
        html.H3(id="button-clicks"),
        html.Hr(),
        dash_table.DataTable(
            id="input_table",
            columns=[
                {"name": ["Feature"], "id": "feature"},
                {"name": ["Rank"], "id": "rank", "editable": False},
                {"name": ["Value"], "id": "value", "editable": True},
            ],
            data=[
                {"feature": "PR Avg_imp", "value": 0.24, "rank": 1},
                {"feature": "YM Avg_imp", "value": 61, "rank": 9},
                {"feature": "API_imp", "value": 45, "rank": 3},
            ],
        ),
        html.Label("Input 1"),
        dcc.Input(id="input-1"),
        html.Label("Input 2"),
        dcc.Input(id="input-2"),
        html.Label("Slider 1"),
        dcc.Input(id="slider-1"),
        html.Button("Submit", id="submit"),
        html.Div(id="output"),
    ]
)


@app.callback(Output("button-clicks", "children"), Input("button", "n_clicks"))
def clicks(n_clicks):
    if n_clicks is None:
        raise PreventUpdate
    else:
        n_clicks = None
        return "Button has been clicked {} times".format(n_clicks)


@app.callback(
    Output("output", "children"),
    Input("submit", "n_clicks"),
    Input("input_table", "data"),
    State("input-1", "value"),
    State("input-2", "value"),
    State("slider-1", "value"),
)
def compute(n_clicks, rows, input1, input2, slider1):
    if ctx.triggered_id == "submit":
        return f"A computation based off of {input1}, {input2}, and {slider1}"
    else:
        print("change_loop")


if __name__ == "__main__":
    app.run_server(debug=True)

1 Like