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.