问题
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