# Callback Decorator in Loop

**URL:** <https://community.plotly.com/t/callback-decorator-in-loop/9126>\
**Category:** Dash Python\
**Created:** [March 22, 2018, 3:30am UTC](https://community.plotly.com/t/callback-decorator-in-loop/9126 "2018-03-22T03:30:38Z")\
**Posts on this page:** 9\
**Page:** 1

<div class="post-metadata">

**Author:** ![sterlingbutters](https://avatars.discourse-cdn.com/v4/letter/s/ed8c4c/32.png) [@sterlingbutters](https://community.plotly.com/u/sterlingbutters)\
**Post date:** [March 22, 2018, 3:30am UTC](https://community.plotly.com/t/callback-decorator-in-loop/9126/1 "2018-03-22T03:30:38Z")

</div>

I can’t seem to get the callback decorator to work in a loop for my problem:

> [@Getting clickData from 'n' number of plots](https://community.plotly.com/t/getting-clickdata-from-n-number-of-plots/9048):
>
> I would like my Dash interface to be able to insert a plot everytime a button is clicked, this I can do. However, I need to get clickData from each subsequent graph. This requires knowing the plot id, thus if there is ‘n’ number of generated plots, how can I change the id’s to be unique in the callback. I can’t do a callback-function in a loop, can I (I tried though I might’ve done something fundamentally wrong)?

(Making this post in case the issue is more general (title))

I looked into this post for my problem:

> [@Handel multiple callback outputs programmatically](https://community.plotly.com/t/handel-multiple-callback-outputs-programmatically/4759):
>
> According to Dash’s user guide, each Dash callback function can only update a single Output property. I have multiple outputs to be updated for a callback input. It is advised to define a callback function for each output. However, it is very tedious when the outputs to be updated are many and very similar. Is there anyway to define a callback function programmatically to handle multiple outputs?

However, it doesn’t seem to work in my case. I posted my code in a stack overflow post: [python - Plotly Dash: Callback Decorator in Loop Not Possible? - Stack Overflow](https://stackoverflow.com/questions/49419718/plotly-dash-callback-decorator-in-loop-not-possible)

But basics resembles:

```
app = dash.Dash()
app.config['suppress_callback_exceptions'] = True

app.layout = html.Div([
html.Button('Create Cell', id='cell-geometry-button', n_clicks=0),
html.Div(id='cell-geometry-config-container'),
html.A(id='click-register'),
])

num_clicks = 0

# Initiate cell geometry config with button
@app.callback(
    Output('cell-geometry-config-container', 'children'),
    [Input('cell-geometry-button', 'n_clicks')],)
def invoke_cell_geometry_options(n_clicks):
    geometry_ui_list = []
    global num_clicks
    num_clicks = n_clicks
    for i in range(n_clicks):
        graph_id = 'cell-graph-{}'.format(i)
        planes_list_id = 'planes-list-{}'.format(i)
        button_id = 'fill-region-button-{}'.format(i)
        click_register_id = 'click-register-{}'.format(i)

        geometry_ui_list.extend([dcc.Graph(id=graph_id),
                                 dcc.Input(id=planes_list_id, placeholder='Enter list of radial planes (comma separated)',
                                           type="text"),
                                 html.Button('Fill Region', id=button_id, n_clicks=0),
                                 html.A(id=click_register_id),
                                 html.Br(),
                        ])

    options = html.Div(geometry_ui_list)
    return options

@app.callback(
    Output('cell-geometry-config-container', 'children'),
    [Input('cell-geometry-button', 'n_clicks')],)
def invoke_cell_geometry_options(n_clicks):
    geometry_ui_list = []
    global num_clicks
    num_clicks = n_clicks
    for i in range(n_clicks):
        graph_id = 'cell-graph-{}'.format(i)
        planes_list_id = 'planes-list-{}'.format(i)
        button_id = 'fill-region-button-{}'.format(i)
        click_register_id = 'click-register-{}'.format(i)

        geometry_ui_list.extend([dcc.Graph(id=graph_id),
                                 dcc.Input(id=planes_list_id, placeholder='Enter list of radial planes (comma separated)',
                                           type="text"),
                                 html.Button('Fill Region', id=button_id, n_clicks=0),
                                 html.A(id=click_register_id),
                                 html.Br(),
                        ])

    options = html.Div(geometry_ui_list)
    return options

for val in range(num_clicks):
    @app.callback(
        Output('click-register-{}'.format(val), 'children'),
        [Input('cell-graph-{}'.format(val), 'clickData')])
    def click_register_function(clickData):
         ...
         return [region, click_x, click_y]

    # Fill Region
    @app.callback(
        Output('cell-graph-{}'.format(val), 'figure'),
        [Input('planes-list-{}'.format(val), 'value'),
         Input('fill-region-button-{}'.format(val), 'n_clicks')],
        [State('material-dropdown', 'value'),
         State('click-register-{}'.format(val), 'children')]
    )
    def fill_region(planes, n_clicks, selected_material, click_register):
        ...
        return figure

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

```

Is there something I am missing?

---

<div class="post-metadata">

**Author:** ![chriddyp](https://sea2.discourse-cdn.com/flex024/user_avatar/community.plotly.com/chriddyp/32/16855_2.png) [@chriddyp](https://community.plotly.com/u/chriddyp)\
**Post date:** [March 22, 2018, 4:53am UTC](https://community.plotly.com/t/callback-decorator-in-loop/9126/2 "2018-03-22T04:53:58Z")

</div>

I think this was posted in another topic, but I’ll link the reference here as well: For some more info on dynamic callbacks, see [Dynamic Controls and Dynamic Output Components](https://community.plotly.com/t/dynamic-controls-and-dynamic-output-components/5519)

---

<div class="post-metadata">

**Author:** ![chriddyp](https://sea2.discourse-cdn.com/flex024/user_avatar/community.plotly.com/chriddyp/32/16855_2.png) [@chriddyp](https://community.plotly.com/u/chriddyp)\
**Post date:** [March 22, 2018, 4:55am UTC](https://community.plotly.com/t/callback-decorator-in-loop/9126/3 "2018-03-22T04:55:02Z")

</div>

> [@sterlingbutters](#):
>
> global num\_clicks

Also, it’s not safe to use `global` variables. Explanation here: [Part 4. Sharing Data Between Callbacks | Dash for Python Documentation | Plotly](https://dash.plot.ly/sharing-data-between-callbacks) as well as in some other threads in the forum like [Global variables sharing with mutex](https://community.plotly.com/t/global-variables-sharing-with-mutex/7171)

---

<div class="post-metadata">

**Author:** ![sterlingbutters](https://avatars.discourse-cdn.com/v4/letter/s/ed8c4c/32.png) [@sterlingbutters](https://community.plotly.com/u/sterlingbutters)\
**Post date:** [March 22, 2018, 5:28am UTC](https://community.plotly.com/t/callback-decorator-in-loop/9126/4 "2018-03-22T05:28:47Z")

</div>

Thank you for the response, that first link you sent looks like it has the answer I’ve been searching for. Will post an update if I figure it out and it looks like I wouldn’t even need the global variable in that case for this part of my code anyway (there is another part but I will post my question in the other thread).

---

<div class="post-metadata">

**Author:** ![sterlingbutters](https://avatars.discourse-cdn.com/v4/letter/s/ed8c4c/32.png) [@sterlingbutters](https://community.plotly.com/u/sterlingbutters)\
**Post date:** [March 22, 2018, 6:21pm UTC](https://community.plotly.com/t/callback-decorator-in-loop/9126/5 "2018-03-22T18:21:32Z")

</div>

I’m afraid that my case is too different from the example in [Dynamic Controls and Dynamic Output Components](https://community.plotly.com/t/dynamic-controls-and-dynamic-output-components/5519) for me to understand an adaptation.

1. I think its safe to say that my output container is also my control container
2. My generate\_output\_callback function doesn’t take any arguments
3. I am taking a single component property (n\_clicks) instead of 2

Below is my attempt of implementation. I noticed that if I change n\_clicks in  
`html.Button('Create Cell', id='cell-geometry-button', n_clicks=0),`

then it works for as many plots as n\_clicks equals. What am I doing wrong still?

```
import dash
import dash_core_components as dcc
import dash_html_components as html
from dash.dependencies import Output, State, Input
import plotly.graph_objs as go
import numpy as np
import re

app = dash.Dash()
app.config['suppress_callback_exceptions'] = True

#######################################################################################################################

app.layout = html.Div([
    html.Div([
        dcc.Dropdown(id='material-dropdown'),
        html.Button('Add Material', id='add-material-button', n_clicks=0),
        html.Div(id='material-options-container'),

        html.Button('Create Cell', id='cell-geometry-button', n_clicks=0),
        html.Div(id='cell-geometry-config-container'),
        html.A(id='click-register'),
    ]),

])

#######################################################################################################################
# Materials Interface
# Keep track of material names
materials_list = []

# Invoke material options
@app.callback(
    Output('material-options-container', 'children'),
    [Input('add-material-button', 'n_clicks')],)
def invoke_material_options(n_clicks):
    if n_clicks > 0:
        options = html.Div([dcc.Input(id='material-name', placeholder='Enter Material Name'),
                            dcc.Input(id='material-density', placeholder='Enter Material Density', type='number'),
                            dcc.Input(id='material-temperature', placeholder='Enter Material Temperature', type='number'),
                            html.Button('Submit Material', id='submit-material-button', n_clicks=0),
                            html.Br()
                            ])
        return options

# Submit material to model
@app.callback(
    # Output('material-message-update', 'children'),
    Output('material-dropdown', 'options'),
    [Input('submit-material-button', 'n_clicks')],
    [State('material-name', 'value'),
     State('material-density', 'value'),
     State('material-temperature', 'value'),
     State('material-dropdown', 'options')])
def submit_material(n_clicks, material_name, material_density, material_temperature, material_options):
    if n_clicks > 0:
        if material_options is not None:
            material_options.append({'label': material_name, 'value': len(material_options)+1})
            materials_list.append(material_name)
        if material_options is None:
            material_options = [{'label': material_name, 'value': 0}]
            materials_list.append(material_name)
        n_clicks = 0
        return material_options

#######################################################################################################################
# Geometry Interface

# Initiate cell geometry config with button
@app.callback(
    Output('cell-geometry-config-container', 'children'),
    [Input('cell-geometry-button', 'n_clicks')],)
def invoke_cell_geometry_options(n_clicks):
    # TODO: Below works but must find way to implement fill_region function on arbitrary number of graphs
    geometry_ui_list = []
    for i in range(n_clicks):
        graph_id = 'cell-graph-{}'.format(i)
        planes_list_id = 'planes-list-{}'.format(i)
        button_id = 'fill-region-button-{}'.format(i)

        geometry_ui_list.extend([dcc.Graph(id=graph_id),
                                 dcc.Input(id=planes_list_id, value='.4, .43', placeholder='Enter list of radial planes (comma separated)',
                                           type="text"),
                                 html.Button('Fill Region', id=button_id, n_clicks=0),
                                 html.Br(),
                        ])

    options = html.Div(geometry_ui_list)
    return options

def generate_output_callbacks():
    def fill_region(planes, n_clicks, selected_material, clickData):
        planes = [float(plane) for plane in planes.split(',')]
        planes.sort()

        edge = planes[-1]
        x = np.linspace(-edge, edge, 250)
        y = np.linspace(-edge, edge, 250)

        regions = []
        cell_hover = []
        # Normal Display
        for i in x:
            row = []
            text_row = []
            for j in y:

                if np.sqrt(i **2 + j** 2) < planes[0]:
                    row.append(7) # <- Arbitrary number to adjust color
                    text_row.append('Region 1')

                if np.sqrt(i **2 + j** 2) > planes[-1]:
                    row.append(5) # <- Arbitrary number to adjust color
                    text_row.append('Region {}'.format(len(planes) + 1))

                for k in range(len(planes) - 1):
                    if planes[k] < np.sqrt(i **2 + j** 2) < planes[k + 1]:
                        row.append(k * 3) # <- Arbitrary number to adjust color
                        text_row.append('Region {}'.format(k + 2))
            regions.append(row)
            cell_hover.append(text_row)

        ######################################################
        # Initialize region
        if clickData is not None:
            if 'points' in clickData:
                point = clickData['points'][0]
                if 'text' in point:
                    region = int(re.search(r'\d+', point['text']).group())
                if 'x' in point:
                    click_x = point['x']
                if 'y' in point:
                    click_y = point['y']

            if n_clicks > 0:
                new_hover = []

                # Change graph on Click # TODO: Figure out why new text wont show up
                if 0 < np.sqrt(click_x **2 + click_y** 2) < planes[0]:
                    for row_ in cell_hover:
                        for text in row_:
                            new_hover.append(
                                text.replace('Region 1', '{} Region'.format(materials_list[selected_material])))

                if np.sqrt(click_x **2 + click_y** 2) > planes[-1]:
                    for row_ in cell_hover:
                        for text in row_:
                            new_hover.append(text.replace('Region {}'.format(len(planes) + 1),
                                                          '{} Region'.format(materials_list[selected_material])))

                for k in range(len(planes) - 1):
                    if planes[k] < np.sqrt(click_x **2 + click_y** 2) < planes[k + 1]:
                        for row_ in cell_hover:
                            for text in row_:
                                new_hover.append(text.replace('Region {}'.format(k + 2),
                                                              '{} Region'.format(materials_list[selected_material])))

                cell_hover = new_hover
                n_clicks = 0

        ######################################################

        heatmap = go.Heatmap(z=regions, x=x, y=y, hoverinfo='x+y+text', text=cell_hover, opacity=0.5, showscale=False)

        data = [heatmap]
        shapes = []

        for plane in planes:
            shape = {
                'type': 'circle',
                'x0': -plane,
                'y0': -plane,
                'x1': plane,
                'y1': plane,
                'line': {
                    'width': 4,
                },
                'opacity': 1
            }

            shapes.append(shape)

        layout = dict(title='Cell Region Depiction',
                      height=1000,
                      width=1000,
                      shapes=shapes)

        figure = dict(data=data, layout=layout)
        return figure

    return fill_region

for val in range(app.layout['cell-geometry-button'].n_clicks):
    app.callback(
        Output('cell-graph-{}'.format(val), 'figure'),
        [Input('planes-list-{}'.format(val), 'value'),
         Input('fill-region-button-{}'.format(val), 'n_clicks')],
        [State('material-dropdown', 'value'),
         State('cell-graph-{}'.format(val), 'clickData')]
    )(generate_output_callbacks())

##################################################

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

---

<div class="post-metadata">

**Author:** ![chriddyp](https://sea2.discourse-cdn.com/flex024/user_avatar/community.plotly.com/chriddyp/32/16855_2.png) [@chriddyp](https://community.plotly.com/u/chriddyp)\
**Post date:** [March 22, 2018, 8:11pm UTC](https://community.plotly.com/t/callback-decorator-in-loop/9126/6 "2018-03-22T20:11:03Z")

</div>

> [@sterlingbutters](#):
>
> ```auto
> for val in range(app.layout['cell-geometry-button'].n_clicks):
> app.callback(
> Output('cell-graph-{}'.format(val), 'figure'),
> [Input('planes-list-{}'.format(val), 'value'),
> Input('fill-region-button-{}'.format(val), 'n_clicks')],
> [State('material-dropdown', 'value'),
> State('cell-graph-{}'.format(val), 'clickData')]
> )(generate_output_callbacks())
> 
> ```

This isn’t possible in Dash because you can’t have dynamic callbacks. Everything needs to be upfront.  
`app.layout['cell-geometry-button'].n_clicks` will not change throughout the lifecycle of the app and even if it did, you can’t register `app.callback` _after_ the app has started.

---

<div class="post-metadata">

**Author:** ![sterlingbutters](https://avatars.discourse-cdn.com/v4/letter/s/ed8c4c/32.png) [@sterlingbutters](https://community.plotly.com/u/sterlingbutters)\
**Post date:** [March 22, 2018, 8:21pm UTC](https://community.plotly.com/t/callback-decorator-in-loop/9126/7 "2018-03-22T20:21:54Z")

</div>

So what would the workaround look like in my case?  
So basically there is no way to render as many plot instances as there are clicks of the button (and still have access to their callback for output)? So you would specify them up-front and the user is limited to as many as you have specified?

Also, I’m not sure I understand why `app.layout['cell-geometry-button'].n_clicks` will not change? Is this different than if I did a callback and retrieved the value from `some-element`?:

```
@app.callback(
        Output('some-element, 'children'),
        [Input('cell-geometry-button', 'n_clicks'),
```

---

<div class="post-metadata">

**Author:** ![chriddyp](https://sea2.discourse-cdn.com/flex024/user_avatar/community.plotly.com/chriddyp/32/16855_2.png) [@chriddyp](https://community.plotly.com/u/chriddyp)\
**Post date:** [March 22, 2018, 8:55pm UTC](https://community.plotly.com/t/callback-decorator-in-loop/9126/8 "2018-03-22T20:55:57Z")

</div>

> [@sterlingbutters](#):
>
> So what would the workaround look like in my case?

The workaround is to define possible combination of things upfront, what I explained in [Dynamic Controls and Dynamic Output Components](https://community.plotly.com/t/dynamic-controls-and-dynamic-output-components/5519). If defining everything upfront isn’t possible, then it’s not possible to build that type of UI is not possible in pure Dash right now. Alternatively, you can build the UI in React and then make it a Dash plugin ([Build Your Own Components | Dash for Python Documentation | Plotly](https://plot.ly/dash/plugins)). I’d like to add support for dynamic components and callbacks to Dash we’ll likely need a [commercial sponsor to help fund that work](https://plot.ly/products/consulting-and-oem/) (it’s a significant amount of work).

> [@sterlingbutters](#):
>
> Also, I’m not sure I understand why app.layout[‘cell-geometry-button’].n\_clicks will not change? Is this different than if I did a callback and retrieved the value from some-element?:
> 
> ```auto
> @app.callback(
> Output('some-element, 'children'),
> [Input('cell-geometry-button', 'n_clicks'),
> 
> ```

Yes, those two things are very different. Dash, like all web apps, works by having a stateless backend and keeping the per-user state is stored in the frontend (the browser). This allows Dash to handle serving multiple users concurrently and to run on multiple processes without having to keep every user’s session in backend memory or keep data synchronized across multiple processes. Callbacks work by asking the front-end (where all the data is stored) to pass the value down to the backend _on demand_: whenever the input changes. Data isn’t automatically synced up between the backend and frontend (that would be too much data to pass over the network), it’s only passed through callbacks.

---

<div class="post-metadata">

**Author:** ![sterlingbutters](https://avatars.discourse-cdn.com/v4/letter/s/ed8c4c/32.png) [@sterlingbutters](https://community.plotly.com/u/sterlingbutters)\
**Post date:** [March 22, 2018, 9:35pm UTC](https://community.plotly.com/t/callback-decorator-in-loop/9126/9 "2018-03-22T21:35:29Z")

</div>

Thank you so much for your help, looks like I will have to work with some limitations until you guys get that funding (I’m rooting for you).

Let me tell you, next, how much I love Plotly, both your products and service. I recommend you guys to anyone remotely interested in computing.  
Apologies if I appear negligent in some things as everything I know is self-taught so there are some things that I still don’t understand.

Thanks again
