Black Lives Matter. Please consider donating to Black Girls Code today.
https://www.blackgirlscode.com

Callback for Dynamically created Graph

So I have a dash board separated in two parts:

  • One if monitoring plots, some parameters over time and over temperature (one point per day)
  • The other one is a set of plot for a specific chosen day

On the two part the user can select the specific plot he wants (there are many).

I want to ba able to click and one point on the monitoring plot and update the daily plot accordingly.

The problem is that I cannot find a way to make a callback to a plot that is dynamically created.

I tried to Output my date picker with the Input of a set of of monitoring graph id (that I am refueling dynamically):

@app.callback(
        Output('date-picker', 'date'),
        [Input("%s-monitoring"%(graph),'clickData') for graph in graph_list]
 )
def monitoring_clicked(*events):
       for e in events:
          if e and e['points']:
               return e['points'][0]['x']
       return current_date

But his does not work because the callback is lost when I am creating the graph with the same id. I though that the call back link object with id when it is called by the app but it seems that it link the objects itself when the callback is created. Isn’t it ?

Does anybody have a clue ? I looked for something like graph.onClick = event_handler but could not find.

2 Likes

AFAIK this functionality doesn’t exist. The callback decorator is run once at execution. There is no way of having dynamic callbacks.

Is it something that is completely unthinkable because of the way dash is made or is it a missing feature ? It will helpmeet to figure out how I can start to use the program for my future developments.

Right now, you need to create all of the callbacks up-front. This means that if you are creating dynamic components, you need to create callbacks for every possible combination of inputs and outputs that could exist.

This means that you need to create a dynamic output with a unique ID for every unique set of Inputs that might exist.

Here’s an example that shows how to create dynamic input and output elements and how to define all of the callbacks up-front:

import dash
from dash.dependencies import Input, Output
import dash_core_components as dcc
import dash_html_components as html
import itertools

app = dash.Dash()
app.layout = html.Div([
    dcc.Dropdown(
        id='datasource-1',
        options=[
            {'label': i, 'value': i} for i in ['A', 'B', 'C']
        ],
    ),
    dcc.Dropdown(
        id='datasource-2',
        options=[
            {'label': i, 'value': i} for i in ['X', 'Y', 'Z']
        ]
    ),
    html.Hr(),
    html.Div('Dynamic Controls'),
    html.Div(
        id='controls-container'
    ),
    html.Hr(),
    html.Div('Output'),
    html.Div(
        id='output-container'
    )
])

def generate_control_id(value):
    return 'Control {}'.format(value)

DYNAMIC_CONTROLS = {
    'A': dcc.Dropdown(
        id=generate_control_id('A'),
        options=[{'label': 'Dropdown A: {}'.format(i), 'value': i} for i in ['A', 'B', 'C']]
    ),
    'B': dcc.Dropdown(
        id=generate_control_id('B'),
        options=[{'label': 'Dropdown B: {}'.format(i), 'value': i} for i in ['A', 'B', 'C']]
    ),
    'C': dcc.Dropdown(
        id=generate_control_id('C'),
        options=[{'label': 'Dropdown C: {}'.format(i), 'value': i} for i in ['A', 'B', 'C']]
    ),

    'X': dcc.Dropdown(
        id=generate_control_id('X'),
        options=[{'label': 'Dropdown X: {}'.format(i), 'value': i} for i in ['A', 'B', 'C']]
    ),
    'Y': dcc.Dropdown(
        id=generate_control_id('Y'),
        options=[{'label': 'Dropdown Y: {}'.format(i), 'value': i} for i in ['A', 'B', 'C']]
    ),
    'Z': dcc.Dropdown(
        id=generate_control_id('Z'),
        options=[{'label': 'Dropdown Z: {}'.format(i), 'value': i} for i in ['A', 'B', 'C']]
    )
}

@app.callback(
    Output('controls-container', 'children'),
    [Input('datasource-1', 'value'),
     Input('datasource-2', 'value')])
def display_controls(datasource_1_value, datasource_2_value):
    # generate 2 dynamic controls based off of the datasource selections
    return html.Div([
        DYNAMIC_CONTROLS[datasource_1_value],
        DYNAMIC_CONTROLS[datasource_2_value],
    ])

def generate_output_id(value1, value2):
    return '{} {} container'.format(value1, value2)

@app.callback(
    Output('output-container', 'children'),
    [Input('datasource-1', 'value'),
     Input('datasource-2', 'value')])
def display_controls(datasource_1_value, datasource_2_value):
    # create a unique output container for each pair of dyanmic controls
    return html.Div(id=generate_output_id(
        datasource_1_value,
        datasource_2_value
    ))

def generate_output_callback(datasource_1_value, datasource_2_value):
    def output_callback(control_1_value, control_2_value):
        # This function can display different outputs depending on
        # the values of the dynamic controls
        return '''
            You have selected {} and {} which were
            generated from {} (datasource 1) and and {} (datasource 2)
        '''.format(
            control_1_value,
            control_2_value,
            datasource_1_value,
            datasource_2_value
        )
    return output_callback

app.config.supress_callback_exceptions = True

# create a callback for all possible combinations of dynamic controls
# each unique dynamic control pairing is linked to a dynamic output component
for value1, value2 in itertools.product(
        [o['value'] for o in app.layout['datasource-1'].options],
        [o['value'] for o in app.layout['datasource-2'].options]):
    app.callback(
        Output(generate_output_id(value1, value2), 'children'),
        [Input(generate_control_id(value1), 'value'),
         Input(generate_control_id(value2), 'value')])(
        generate_output_callback(value1, value2)
    )

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

6 Likes