✊🏿 Black Lives Matter. Please consider donating to Black Girls Code today.
⚡️ Concerned about the grid? Kyle Baranko teaches how to predicting peak loads using XGBoost. Register for the August webinar!

Multiple Dash apps - components id collisions

Hello,
I’m trying to leverage Dash to create interactive plots of mathematical functions inside Jupyter Notebook. Therefore, I’ve embedded the Dash app logic into MyClass. Whenever I need to plot a mathematical function, I create a Python function which is going to update the traces of the figure, and pass it to the constructor of MyClass.

This is my code:

import numpy as np
import plotly.graph_objects as go
from jupyter_dash import JupyterDash
import dash_core_components as dcc
import dash_html_components as html
import dash_bootstrap_components as dbc
from dash.dependencies import Input, Output, State, ALL
from dash.development.base_component import Component

class MyClass:
    # count how many instances have been created
    currently_open = 0

    def __init__(self, user_func):
        type(self).currently_open += 1

        self.fig = go.Figure()

        childrens = []
        childrens.append(
            dbc.Container([
                self._create_slider(0, "Par a", 1, 5, 2.5),
                self._create_slider(1, "Par b", 0, 1, 0.15),
            ])
        )

        # Add a Plotly Graph Object
        childrens.append(
            dcc.Graph(id="my_plot" + str(type(self).currently_open), figure={})
        )
        
        # create the Dash app applying the specified CSS and JS
        self.app = JupyterDash(
            "IPlot-" + str(type(self).currently_open),
            external_stylesheets=[dbc.themes.SLATE],
        )
        # add the controls
        self.app.layout = html.Div(childrens)

        # Create the callback, specifying the input/output controls
        @self.app.callback(
            [Output("my_plot" + str(type(self).currently_open), "figure"),
             *[Output('value_slider_{}'.format(j), 'children') for j in range(2)]],
            [Input({'type': 'control_', 'index': ALL}, 'value')]
        )
        def func(values):
            user_func(self.fig, values)
            return [self.fig, *values]
        
        # execute the app in the Jupyter environment
        self.app.run_server(mode="inline")

    def _create_slider(self, i, _name, _min, _max, _val):
        return dbc.Row(
                children = [
                    # this shows the label
                    dbc.Col(html.Div(_name), 
                            width=1, style = {"text-align": "right"}),
                    # this will show the actual value of the slider
                    dbc.Col(html.Div(id="value_slider_" + str(i), children={}), 
                            width=1, style = {"text-align": "left"}),
                    dbc.Col(dcc.Slider(
                        id = {
                            "type": "control_",
                            "index": i
                        },
                        min = _min,
                        max = _max,
                        step = (_max - _min) / 50,
                        value = _val,
                    )),
                ]
            )

Now, let’s suppose I want to plot two different mathematical functions. I create a first instance, everything works ok in the first app:

def my_sin_func(fig, values):
    a, b = values
    fig.data = []
    xx = np.linspace(0, 20, 200)
    yy = a * np.sin(xx) * np.exp(-xx * b)
    fig.add_trace(
        go.Scatter(x=xx, y=yy, mode="lines")
    )
    fig.add_trace(
        go.Scatter(x=xx, y=np.sin(xx), mode="lines")
    )

a = MyClass(my_sin_func)

Then. I create the second instance, everything works ok on the second app:

def my_cos_func(fig, values):
    a, b = values
    fig.data = []
    xx = np.linspace(0, 20, 200)
    yy = a * np.cos(xx) * np.exp(-xx * b)
    fig.add_trace(
        go.Scatter(x=xx, y=yy, mode="lines")
    )
    fig.add_trace(
        go.Scatter(x=xx, y=np.cos(xx), mode="lines")
    )

b = MyClass(my_cos_func)

But as soon as I move back to the first app and change a slider, an error happens in the second app:

KeyError: '..my_plot1.figure...value_slider_0.children...value_slider_1.children..'

I thought that different apps would somehow use a local scope, instead it seems the interactive controls of the two apps are colliding. A possible solution would be to generate unique IDs for each control every time an instance is created, so that the controls of the instance a have a different ID than the instance b… Is there something else I can try? Is there some option I can change?

Thank you in advance.

I’m not sure how JupyterDash handles multiple instances so this won’t answer the core of your problem but you could solve the symptoms by adding the instance number in the ids of the components:

# in __init__()
    self.instance = type(self).currently_open

# in _create_slider()
    html.Div(id=f"value_slider_{self.instance}_{i}", children=[])

Nope, it doesn’t work. I believe the JupyterDash server gets somehow confused. As a matter of fact, by running the app with self.app.run_server(), we can see that both instances uses the same server. Is it possible to start a new server for each instance?

Ok, solved.

By looking at the source code, the run_server method can also accepts the options host and port . So we can start multiple servers. Brilliant!