As it turns out, my first post is an excellent example of of what not to do - it results in a callback nightmare. And that got me thinking - I don’t need to give the user the ability to make endless plots. Providing them with enough plots does the job just fine and hiding the initial plots gives the illusion that they are being dynamically created.
Here is a much more stable solution:
import dash
from dash.dependencies import Input, Output
import dash_html_components as html
import dash_core_components as dcc
import colorlover as cl
import datetime as dt
import pandas as pd
import numpy as np
app = dash.Dash(
external_stylesheets = ['https://codepen.io/chriddyp/pen/bWLwgP.css']
)
server = app.server
colorscale = cl.scales['9']['qual']['Paired']
######
# Generate some random time series data
######
n=3600 # Points
N=100 # Channels
WORDS=['Signal_{}'.format(i) for i in range(N)]
data = np.random.randn(n,N)
df = pd.DataFrame(np.random.randn(n, N), columns=WORDS[0:N])
# Make 10 placement graphs for use later
graph_div = []
for i in range(10):
graph_div.append(html.Div(
children=[
dcc.Graph(id='igraph_{}'.format(i), figure={'data': []},style={'display': 'none'},className="four columns"),
html.Div(id="graph_{}".format(i))]))
# Basic layout
app.layout = html.Div([
html.Div(id='hidden-div', style={'display':'none'}),
html.Div([
html.H2('Time History Plotter',
style={'display': 'inline',
'float': 'left',
'font-size': '2.65em',
'margin-left': '7px',
'font-weight': 'bolder',
'font-family': 'Product Sans',
'color': "rgba(117, 117, 117, 0.95)",
'margin-top': '20px',
'margin-bottom': '0'
}),
html.Img(src="https://s3-us-west-1.amazonaws.com/plotly-tutorials/logo/new-branding/dash-logo-by-plotly-stripe.png",
style={
'height': '100px',
'float': 'right'
},
),
]),
dcc.Dropdown(
id='Signals',
options=[{'label': s, 'value': str(s)}
for s in df],
value=[],
multi=True
),
html.Div(id='container'),
# Add those 10 graphs in
html.Div(graph_div),
])
app.config['suppress_callback_exceptions']=True
# 10 call backs
@app.callback(Output('container', 'children'), [
Input('Signals', 'value'),
Input('igraph_0', 'relayoutData'),
Input('igraph_1', 'relayoutData'),
Input('igraph_2', 'relayoutData'),
Input('igraph_3', 'relayoutData'),
Input('igraph_4', 'relayoutData'),
Input('igraph_5', 'relayoutData'),
Input('igraph_6', 'relayoutData'),
Input('igraph_7', 'relayoutData'),
Input('igraph_8', 'relayoutData'),
Input('igraph_9', 'relayoutData'),
])
def graph_update(Sigs,RD0,RD1,RD2,RD3,RD4,RD5,RD6,RD7,RD8,RD9):
relayoutData=RD0
# Do any of the RD0 have xaxis.range?
for RD in [RD0,RD1,RD2,RD3,RD4,RD5,RD6,RD7,RD8,RD9]:
if RD is not None and 'xaxis.range[0]' in RD:
relayoutData=RD
break
if relayoutData is None:
return replot(Sigs,'autoX')
#return
elif 'xaxis.range[0]' in relayoutData:
XR0=relayoutData['xaxis.range[0]']
XR1=relayoutData['xaxis.range[1]']
return replot(Sigs,[XR0,XR1])
elif 'xaxis.autorange' in relayoutData:
return replot(Sigs,'autoX')
elif Sigs:
return replot(Sigs,'autoX')
else:
return
def replot(Sigs,X):
graphs = []
for i, Sig in enumerate(Sigs):
dff = df[Sig]
time = pd.date_range('2016-07-01', periods=len(dff), freq='S')
trace = [{
'x': time, 'y': dff,
'type': 'scatter', 'mode': 'lines',
'line': {'width': 1, 'color': colorscale[(i*2) % len(colorscale)]},
#'hoverinfo': 'none',
#'legendgroup': Sig,
'showlegend': True,
'name': '{}'.format(Sig)
}]
graphs.append(html.Div(
children=[
dcc.Graph(
id='igraph_{}'.format(i),
figure={
'data': trace,
'layout': {
'legend': {'x': 0},
'margin': {'l': 40, 'r': 20, 't': 20, 'b': 40},
'xaxis': {
'title': 'Time [s]',
'autorange': True if X == 'autoX' else False,
'range': False if X == 'autoX' else X
},
'yaxis': {
'title': 'Time [s]'
},
}
}, className="four columns",
),
html.Div(id="graph_{}".format(i)),
]))
return html.Div(graphs)
if __name__ == '__main__':
app.run_server(debug=True, host='0.0.0.0')