I have been experimenting with streaming data to graphs. I use an interval element’s n_clicks property as the input and I output using a graph’s extendData property (+ just a div I can write text to).
Using regular Python callbacks I can get this working, but it is not as smooth as I would like (I tried both with animate=True and animate=False on the graph but do not see much difference).
I suspected this behavior was because I was making so many server requests, so I changed it to be entirely client-side (using synthetic data for now, that I can easily generate either on the server or client). However, things get much worse in this case, and the graph only seems to be updating at about 1Hz even though the callback is being triggered much more frequently (and my text in the div is clearly getting update more frequently).
Any suggestions for how to make these updates appear more smooth? Why does setting animate=True not appear to have any effect? Why do I see these slow figure updates when using client side callbacks?
Thanks!
For reference here is my code I am playing with
import datetime
import dash
import dash_core_components as dcc
import dash_html_components as html
import plotly
import plotly.graph_objects as go
from plotly.subplots import make_subplots
from dash.dependencies import Input, Output, ClientsideFunction
import datetime
import numpy as np
import time
CLIENTSIDE = True
external_stylesheets = ['https://codepen.io/chriddyp/pen/bWLwgP.css']
app = dash.Dash(__name__, external_stylesheets=external_stylesheets)
fig = go.Figure()
fig.add_trace(go.Scattergl(x=[n * .1 for n in range(100)], y=[None for _ in range(100)], mode='lines'))
fig.update_yaxes(range=[-1, 1])
app.layout = html.Div(
html.Div([
html.H4('Stream Test'),
html.Div(id='live-update-text'),
dcc.Graph(id='live-update-graph', figure=fig, animate=False),
dcc.Interval(
id='interval-component',
interval=50, # in milliseconds
n_intervals=0
)
])
)
if not CLIENTSIDE:
start_time = None
app_start_time = time.time()
@app.callback([Output('live-update-graph', 'extendData'), Output('live-update-text', 'children')],
[Input('interval-component', 'n_intervals')])
def update_graph_live(n):
global start_time
if start_time:
elapsed = time.time() - start_time
values = np.arange(start_time - app_start_time, start_time + elapsed - app_start_time, 0.05)
y=[np.sin(i) for i in values]
return_values = (dict(y=[y], x=[values]), 0, 200), str(n) + ' -- ' + datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S.%f')[:-3]
else:
return_values = (dash.no_update, dash.no_update)
start_time = time.time()
return return_values
else:
app.clientside_callback(
ClientsideFunction(
namespace='clientside',
function_name='updateGraph'
),
[Output('live-update-graph', 'extendData'), Output('live-update-text', 'children')],
[Input('interval-component', 'n_intervals')]
)
if __name__ == '__main__':
app.run_server(debug=True, host='0.0.0.0')
Javascript:
if(!window.dash_clientside) {window.dash_clientside = {};}
window.dash_clientside.clientside = {
updateGraph: function (n) {
if (!window.loadStartTime) {
window.loadStartTime = Math.floor(Date.now() / 1000);
}
if (window.startTime) {
let elapsed = Math.floor(Date.now() / 1000) - window.startTime;
var values = new Array(Math.floor(elapsed / 0.05));
var y = new Array(Math.floor(elapsed / 0.05));
for(var i = 0; i < values.length; i++){
values[i] = window.startTime - window.loadStartTime + i * 0.05;
y[i] = Math.sin(values[i]);
}
returnValues = [[{'y': [y], 'x': [values]}, 0, 200], n.toString()]
} else {
returnValues = [window.dash_clientside.no_update, window.dash_clientside.no_update]
}
window.startTime = Math.floor(Date.now() / 1000);
//console.log(returnValues);
return returnValues;
}
};