I am creating a live updating graph using plotly/dash and python. Passing the variable self in python is unfortunately throwing errors. I have made a minmimal example below to outline the problem.
import dash
from dash.dependencies import Output, Input
import dash_core_components as dcc
import dash_html_components as html
import plotly
import random
import plotly.graph_objs as go
from collections import deque
import time
import numpy as np
class PlotlyDashboard:
_X = deque(maxlen = 20)
_X.append(1)
_Y = deque(maxlen = 20)
_Y.append(1)
_app = dash.Dash(__name__)
def __init__(self):
self._app.layout = html.Div(
[
dcc.Graph(id = 'live-graph', animate = True),
dcc.Interval(
id = 'graph-update',
interval = 1000,
n_intervals = 0
),
]
)
@_app.callback(
Output('live-graph', 'figure'),
[Input('graph-update', 'n_intervals') ]
)
def update_graph_scatter(self, n):
data = plotly.graph_objs.Scatter(
x=list(self._X),
y=list(self._Y),
name='Scatter',
mode= 'lines+markers'
)
return {'data': [data],
'layout' : go.Layout(xaxis=dict(range=[min(self._X),max(self._X)]),yaxis = dict(range = [min(self._Y),max(self._Y)]),)}
def update(self, y):
self._X.append(self._X[-1] + 1)
self._Y.append(y)
def start(self):
self._app.run_server(port=8050)
live_plotter = PlotlyDashboard()
live_plotter.start()
while True:
live_plotter.update(np.random.normal(0,1))
time.sleep(1)
The graph updates once a second with new data that is provided via the update function. The update function updates the data queues stored in self. During the @app.callback which also happens every second it consults self to get the current state of the data before plotting, but I get the below error when doing so
output_value = func(*func_args, **func_kwargs) # %% callback invoked %%
TypeError: update_graph_scatter() missing 1 required positional argument: 'n'
The error goes away if I remove self as an arg in the update_graph_scatter method, but then the internals of the method cannot access self which causes a different error. Any help that can be provided on how I can access self or an equivalent common store that both the update method and @app.callback tagged method can consult will be much appreciated.
Edit: By using rob's solution and a bit of threading I was able to solve the issue. Please see my solution below on how to instantiate the class in one thread (must be done because app_server is blocking) and then update the data the class is plotting using the main thread.
import dash
from dash.dependencies import Output, Input
import dash_core_components as dcc
import dash_html_components as html
import plotly
import random
import plotly.graph_objs as go
from collections import deque
import time
import numpy as np
from threading import Thread
class PlotlyDashboard:
_X = deque(maxlen = 20)
_X.append(1)
_Y = deque(maxlen = 20)
_Y.append(1)
_app = dash.Dash(__name__)
def __init__(self):
self._app.layout = html.Div(
[
dcc.Graph(id = 'live-graph', animate = True),
dcc.Interval(
id = 'graph-update',
interval = 1000,
n_intervals = 0
),
]
)
if self._app is not None and hasattr(self, "callbacks"):
self.callbacks(self._app)
def callbacks(self, _app):
@_app.callback(
Output("live-graph", "figure"), [Input("graph-update", "n_intervals")]
)
def update_graph_scatter(n):
# let's update data here to show class callbacks are working
data = plotly.graph_objs.Scatter(
x=list(self._X), y=list(self._Y), name="Scatter", mode="lines+markers"
)
return {
"data": [data],
"layout": go.Layout(
xaxis=dict(range=[min(self._X), max(self._X)]),
yaxis=dict(range=[min(self._Y), max(self._Y)]),
),
}
def update(self, y):
self._X.append(self._X[-1] + 1)
self._Y.append(y)
def start(self):
self._app.run_server(port=8050)
live_plotter = PlotlyDashboard()
def starter():
live_plotter.start()
plot_thread = Thread(target=starter)
plot_thread.start()
while True:
live_plotter.update(np.random.normal(0,1))
time.sleep(1)