Faster re-drawing of dcc.Graph?

I’m writing a Dash app with a date slider, where a different subset of data is plotted depending on the date. I’ve implemented an example below and have found the re-rendering to be very slow. All that’s changing is which subset of data is being drawn, which should be fast to redraw.

Live heroku example here - https://andrew-mwe.herokuapp.com

The two main issues I’m seeing are:
• Re-drawing the graph seems slower than it should be. The layout is fixed, all that’s changing are the 12 data points
• There’s no debouncing or throttling. If you drag the slider along, a backlog of graphs to draw forms and you have to wait while the title bar says “updating”

Any input would be appreciated.

Thanks!

import dash
from dash.dependencies import Input, Output, State
import dash_html_components as html
import dash_core_components as dcc
import plotly.graph_objs as go
import numpy as np
import pandas as pd

# Get data
start = np.datetime64('2015-01-01')
end = np.datetime64('2019-07-25')
startdt = start.astype('datetime64[ns]')
enddt = end.astype('datetime64[ns]')
terms = [1,12,24,36,48,60]
def generatedata(term):
    shock = np.random.randint(10,20)
    date_rng = np.arange(start,end)
    df = pd.DataFrame(date_rng, columns=['Date'])
    df['Rate'] = np.random.randint(0,100,size=(len(date_rng)))
    df['Shocked'] = df['Rate']+shock
    # simulate 10% days missing
    df = df.sample(frac=0.8)
    df=df.sort_values(by='Date')
    df['Term']=term
    return df
def generatedata_loop(terms):
    return pd.concat(generatedata(t) for t in terms).sort_values(by=['Term','Date'])
curves = ['CurveA','CurveB','CurveC'] # 3 curves worth of data
data = {x:generatedata_loop(terms) for x in curves}
stepsize = np.timedelta64(30, 'D') / np.timedelta64(1, 'ns') # step every 30 days
datelist = {x:data[x]['Date'].unique() for x in curves}

# Initial figure
X = data['CurveA'].loc[data['CurveA']["Date"]==start]
traces=[go.Scatter(x=X['Term'], y=X['Rate'], name='Baseline')]
traces.append(go.Scatter(x=X['Term'], y=X['Shocked'], name='Shocked'))
layout=go.Layout(showlegend=True,uirevision='static',
                 xaxis={'title': 'Term (months)','title_text':'Rate (%)'},
                 yaxis={'title': 'Rate (%)', 'title_text':'Term (months)','range':[0,120]},
                 title_text=str(start)[:10])
figorig = go.Figure(data=traces, layout=layout)

# Subroutines
def closestvaliddate(inp,datelist):
    dist = abs(datelist-np.datetime64(inp,"ns")) # input is date in integer form
    mindist = min(dist) #  divide by np.timedelta64(1, 'D') to get days
    return datelist[dist==mindist][0]

# Start application
external_stylesheets = ['https://codepen.io/chriddyp/pen/bWLwgP.css']
app = dash.Dash(__name__, external_stylesheets=external_stylesheets)
server = app.server

######################################
####### App layout ###################
######################################
app.layout = html.Div([
    dcc.Dropdown(id="curve-type", multi=False, value="CurveA", style={"width": "30%"},
                 options=[{"label": x, "value": x} for x in curves]),
    dcc.Graph(id='graph-shocks'),
    html.Div(children=[dcc.Slider(id='date-slider',min=startdt,max=enddt,step=stepsize,value=startdt,
         updatemode='drag',marks={int(x):str(x)[:10] for x in [startdt,enddt]})
           ], style={"width": "80%","margin":"auto"}) 
])

@app.callback(Output('graph-shocks', 'figure'),
    [Input('date-slider', 'value'),Input('curve-type', 'value')]) # , [State('graph-shocks', 'figure')]
def update_figure(inpdate,curve):
    selecteddate = closestvaliddate(inpdate,datelist[curve])
    X = data[curve].loc[data[curve]["Date"]==selecteddate]
    traces = [go.Scatter(x=X['Term'], y=X['Rate'], name='Baseline')]
    traces.append(go.Scatter(x=X['Term'], y=X['Shocked'], name='Shocked'))
    fig = go.Figure(data=traces, layout=layout)
    fig.layout.update(title_text=str(selecteddate)[:10])
    return fig

if __name__ == "__main__":
    app.run_server(debug=True)

Take at look at the updatemode property. From the docs:

Thanks @russellthehippo for the suggestion, but I don’t want to change updatemode to mouseup. I’m wanting to address the two issues I mentioned (faster redrawing and introducing debouncing / throttling).

Conceptually, I could render the data as a 3d plot and have fast rendering, but I want to present in 2d. What I’m doing now is essentially showing cross sections of a 3d plot, where the slider controls which cross section is being taken. It’s just much slower than rendering a 3d plot and doesn’t have a nice user-experience.

Is Plotly.restyle (or Plotly.react) an option to address the redraw speed?

See plotly javascript documentation here https://plot.ly/javascript/plotlyjs-function-reference/#plotlyrestyle

This function has comparable performance to Plotly.react and is faster than redrawing the whole plot with Plotly.newPlot.

(I’m a little confused as to whether everything from plotly javascript is also available in plotly python or if they are separate)

Thanks

The component already uses Plotly.react - See the source code here: dash-core-components/Graph.react.js at 5ceae700d915e2df54829098ed8574dd634dd783 · plotly/dash-core-components · GitHub

Ah, I see.

Do you have any thoughts as to where speed could be improved in redrawing the graph?

Thanks