Plotly-Dash stock app in python, with clientside callback (yaxis autoscale on xaxis zoom)

时光总嘲笑我的痴心妄想 提交于 2021-01-05 07:07:40

问题


I'm creating a simple stock chart app in dash-plotly (python) whith an xaxis slider. When sliding the xaxis, I want the yaxis to dynamically rescale to the view. I think I have managed to get the callback function to trigger when sliding the xaxis scale through the 'relayoutData' hook. But instead of updating the yaxis the script throws errors. I'm not sure of the proper syntax to update layout from a callback function. Any ideas?

Here is my code so far. It runs, but yaxis is set at run time, and doesn't update.

Thanks a lot for any help =)

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

#some random values
a = datetime.datetime.today()
numdays = 100
dateList = []
for x in range (0, numdays):
    dateList.append(a - datetime.timedelta(days = x))
xy = [dateList,np.random.rand(100)]

app = dash.Dash()

app.title = 'Random'
dataS = go.Scatter(
        x = xy[0],
        y = xy[1],
        name = 'Rand',
        mode = 'lines'
    )
layoutS = go.Layout(
    title="Rand",
    xaxis=dict(
        rangeslider_visible=True,
        rangeselector=dict(
            buttons=list([
                dict(count=1, label="1m", step="month", stepmode="backward"),
                dict(count=6, label="6m", step="month", stepmode="backward"),
                dict(count=1, label="YTD", step="year", stepmode="todate"),
                dict(count=1, label="1y", step="year", stepmode="backward"),
                dict(count=5, label="5y", step="year", stepmode="backward"),
                dict(step="all")
            ])
        )
    )
)

app.layout = html.Div(
    html.Div([
        html.H1(children='Random nums'),
        html.Div(children='''
            Rand rand rand.
        '''),

        dcc.Graph(id='RandGraph', animate=True, figure=go.FigureWidget(data=dataS,layout=layoutS))
    ])
)

@app.callback(Output('RandGraph','figure'),[Input('RandGraph','relayoutData')])
def update_graph(relOut):
    layout = go.Layout(
        yaxis=dict(range=[min(y),max(y)])
    )
    return {'layout':layout}

if __name__ == '__main__':
    app.run_server(debug=False)

回答1:


Here is the result. It I tried a serverside implementation first, which ended up being really slow (I kept it for reference). The clientside impletation is a lot more responsive and faster. A joy to use. I just needed to learn a bit of javascript to make if work =) I sure was missing pandas when I was trying to do the array filtering!

Here is the working code. Enjoy!

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

#some random values
a = datetime.datetime.today()
numdays = 100
dateList = []
for x in range (0, numdays):
    dateList.append(a - datetime.timedelta(days = x))
xy = [dateList,np.random.rand(100)]
df = pd.DataFrame(data=xy[1],columns=["y"],index=xy[0])


#Graph data
dataS = [dict(
    x = df.index,
    y = df['y'],
    name = 'meter',
    mode = 'lines'
)]

#Graph layout
layoutS = go.Layout(
    title="Meter",
    xaxis=dict(
        rangeslider_visible=True,
        rangeselector=dict(
            buttons=list([
                dict(count=1, label="1m", step="month", stepmode="backward"),
                dict(count=6, label="6m", step="month", stepmode="backward"),
                dict(count=1, label="YTD", step="year", stepmode="todate"),
                dict(count=1, label="1y", step="year", stepmode="backward"),
                dict(count=5, label="5y", step="year", stepmode="backward"),
                dict(step="all")
            ])
        )
    ),
    yaxis=dict(range=[0,2])
)

#Dash app layout
app = dash.Dash()
app.title = 'Random'

app.layout = html.Div(
    html.Div([
        html.H1(children='Random nums'),
        html.Div(children='''
            Rand rand rand.
        '''),

        dcc.Input(
            id='input-y',
            placeholder='Insert y value',
            type='number',
            value='',
        ),
        html.Div(id='result'),

        dcc.Graph(id='RandGraph',figure=dict(data=dataS,layout=layoutS))
    ])
)

#client side implementation
app.clientside_callback(
    """
    function(relOut, Figure) {
        if (typeof relOut !== 'undefined') {
            if (typeof relOut["xaxis.range"] !== 'undefined') {
                //get active filter from graph
                fromS = new Date(relOut["xaxis.range"][0]).getTime()
                toS = new Date(relOut["xaxis.range"][1]).getTime()

                xD = Figure.data[0].x
                yD = Figure.data[0].y

                //filter y data with graph display
                yFilt = xD.reduce(function (pV,cV,cI){
                    sec = new Date(cV).getTime()
                    if (sec >= fromS && sec <= toS) {
                        pV.push(yD[cI])
                    }
                    return pV
                }, [])

                yMax = Math.max.apply(Math, yFilt)
                yMin = Math.min.apply(Math, yFilt)
            } else { 
                yMin = Math.min.apply(Math, Figure.data[0].y)
                yMax = Math.max.apply(Math, Figure.data[0].y) 
            }
        } else { 
            yMin = Math.min.apply(Math, Figure.data[0].y)
            yMax = Math.max.apply(Math, Figure.data[0].y) 
        }
        Figure.layout.yaxis = {
            'range': [yMin,yMax],
            'type': 'linear'
        }
        return {'data': Figure.data, 'layout': Figure.layout};
    }
    """,
    Output('RandGraph','figure'),
    [Input('RandGraph','relayoutData')],[State('RandGraph', 'figure')]
)

#Server side implementation (slow)
#@app.callback(
#    Output('RandGraph','figure'),
#    [Input('RandGraph','relayoutData')],[State('RandGraph', 'figure')]
#)
#def update_result(relOut,Fig):
#    ymin = df.loc[relOut['xaxis.range'][1]:relOut['xaxis.range'][0],'y'].min()
#    ymax = df.loc[relOut['xaxis.range'][1]:relOut['xaxis.range'][0],'y'].max()
#    newLayout = go.Layout(
#        title="OL Meter",
#        xaxis=dict(
#            rangeslider_visible=True,
#            rangeselector=dict(
#                buttons=list([
#                    dict(count=0, label="1m", step="month", stepmode="backward"),
#                    dict(count=6, label="6m", step="month", stepmode="backward"),
#                    dict(count=1, label="YTD", step="year", stepmode="todate"),
#                    dict(count=1, label="1y", step="year", stepmode="backward"),
#                    dict(count=5, label="5y", step="year", stepmode="backward"),
#                    dict(step="all")
#                ])
#            ),
#            range=relOut['xaxis.range']
#        ),
#        yaxis=dict(range=[ymin,ymax])
#    )
#    
#    
#    Fig['layout']=newLayout
#    return Fig

if __name__ == '__main__':
    app.run_server(debug=False)


来源:https://stackoverflow.com/questions/62166896/plotly-dash-stock-app-in-python-with-clientside-callback-yaxis-autoscale-on-xa

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!