Show and Tell - Time History Plotting with Dynamic Plots and linked X axis Zooming

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')
2 Likes