I tried to make a minimal (non-working) example. There are 3 layout components:
-a hidden div containing an interval component to do the polling
-a datatable that can be clicked by the user
-the graph displaying the data
There are 3 callbacks:
-updateOutputGraph displays a graph, when the user clicks the datatable, or when new data is received
-checkNewData polls data
-the client-side callback verifies
It works as follows. The server is polled, if new data is returned, is_new_data is set to True. This triggers the client-side callback. The condition (is_new_data == true) is checked, and if true, the dummy-button is clicked which triggers the updateOutputGraph callback. In cases where there is no new data, the update is not unnecessarily triggered.
While this approach is convoluted, it effectively realizes a conditional callback.
app.layout = html.Div(children =[
# Hidden div that does the polling
html.Div(children = [
dcc.Interval(
id='interval-component-10s',
interval=10*1000,
n_intervals=0
),
html.Div(id = "new_data", children =[]), # This div stores the new data
html.Div(id = "is_new_data", children = [False]),# This div keeps track of whether there is new data
dbc.Button("dummy_button", id="dummy_button"), # This button will be clicked by the client-side callback
html.Div(id = "dummy2", children = []) # Dummy div for the output,
], style= {"display":'none'}
),
# User can click this dataframe to display a certain row
html.Div(
children=[
dash_table.DataTable(
id= "data_selector",
columns =[{"name": i, "id":i} for i in dataframe.columns],
data = dataframe.to_dict('records'),
)
]
),
#Output is shown on the graph
dcc.Graph(
id='some_graph',
figure=getSomeFigure()
)
])
# This callback updates the displayed figure, either triggered by the user, or through new data by polling the server
@app.callback(
[Output("some_graph",'figure')],
[Input('data_selector', 'active_cell'),
Input("dummy_button", 'n_clicks')],
[State('data_selector','data'),
State("new_data", 'children')]
)
def updateOutputGraph(active_cell,dummy, data_records, new_data):
# Check which input has triggered the callback
ctx = dash.callback_context
if not ctx.triggered:
button_id = 'No clicks yet'
else:
[button_id, propertyName] = ctx.triggered[0]['prop_id'].split('.');
# If the user has triggered the callback by clicking the datatable, use the selected data
if (button_id = 'data_selector'):
# extract the correct data out of the dataframe
extracted_data = extractTheData(data_records, active_cell)
# The event was triggered by the dummy button
else:
extracted_data = new_data
# possibly lenghty calculations using extracted_data
return makeFigure(extracted_data)
# Callback that checks for new data every 10s, if there is new data, store it, and set is_new_data to true
@app.callback(
[Output("new_data", 'children'),
Output("is_new_data", 'children')],
[Input('interval-component-10s', 'n_intervals')],
[State("new_data", 'children')])
def updateNewData(n_intervals, previous_new_data):
new_data = checkIfNewData()
if (new_data != None):
return [new_data], [True]
else:
return previous_new_data,[False]
# The client-side callback checks whether there is new data, as stored in is_new_data
app.clientside_callback(
"""
function(is_new_data) {
// return value does not matter
// probably need to take the first child of is_new_data
if (is_new_data == true){
dummy_button = document.getElementById("dummy_button")
dummy_button.click()
return [0]
}
return [0]
}
""",
Output("dummy2", 'children'),
[Input("is_new_data", 'children')]
)