Plotly 垂直范围不会自动调整
Plotly vertical range is not automatically adjusted
下面的代码制作带有范围滑块的烛台图。如果我使滑块变窄,我想放大垂直比例。这是怎么做到的?我希望它有某种设置,但我找不到。目前结果可能看起来像屏幕截图;显然不是最优的。垂直刻度的很大一部分未使用。如何解决?
import sys
import pandas as pd
import plotly.graph_objects as go
from datetime import datetime
from Downloader import CDownloader
# import matplotlib.dates as mdates # Styling dates
class CGraphs:
def Candlestick(self, aSymbolName:str):
# Warning, this function reads from disk, so it is slow.
print(sys._getframe().f_code.co_name, ": Started. aSymbolName ", aSymbolName)
downloader : CDownloader = CDownloader()
df_ohlc : pd.DataFrame = downloader.GetHistoricalData(aSymbolName)
print("df_ohlc", df_ohlc)
graph_candlestick = go.Figure()
candle = go.Candlestick(x = df_ohlc['Date'],
open = df_ohlc['Open'],
high = df_ohlc['High'],
low = df_ohlc['Low'],
close = df_ohlc['Close'],
name = "Candlestick " + aSymbolName)
graph_candlestick.add_trace(candle)
graph_candlestick.update_xaxes(title="Date", rangeslider_visible=True)
graph_candlestick.update_yaxes(title="Price", autorange=True)
graph_candlestick.update_layout(
title = aSymbolName,
height = 600,
width = 900,
showlegend = True)
graph_candlestick.update_layout(xaxis_rangebreaks = [ dict(bounds=["sat", "mon"]) ])
graph_candlestick.show()
print(sys._getframe().f_code.co_name, ": Finished. aSymbolName ", aSymbolName)
graphs:CGraphs = CGraphs()
graphs.Candlestick("MSFT")
此功能在 plotly-python
中不可用,目前是 Plotly 团队的 open issue。
我认为您可以在 plotly-dash
中构建此功能,因为此库支持回调。例如,使用 server-side 实现(@kkollsg 在 this forum 上的回答提供了很多帮助):
import dash
from dash import Output, Input, State, dcc, html
import plotly.graph_objs as go
import numpy as np
import pandas as pd
import datetime
class CGraphs:
def makeCandlestick(self, aSymbolName:str):
# Warning, this function reads from disk, so it is slow.
# print(sys._getframe().f_code.co_name, ": Started. aSymbolName ", aSymbolName)
# downloader : CDownloader = CDownloader()
# df_ohlc : pd.DataFrame = downloader.GetHistoricalData(aSymbolName)
## load some similar stock data
df_ohlc = pd.read_csv('https://raw.githubusercontent.com/plotly/datasets/master/finance-charts-apple.csv')
df_ohlc.rename(columns=dict(zip(['AAPL.Open', 'AAPL.High', 'AAPL.Low', 'AAPL.Close'],['Open','High','Low','Close'])), inplace=True)
# print("loading data")
# print("df_ohlc", df_ohlc)
graph_candlestick = go.Figure()
candle = go.Candlestick(x = df_ohlc['Date'],
open = df_ohlc['Open'],
high = df_ohlc['High'],
low = df_ohlc['Low'],
close = df_ohlc['Close'],
name = "Candlestick " + aSymbolName)
graph_candlestick.add_trace(candle)
graph_candlestick.update_xaxes(title="Date", rangeslider_visible=True)
graph_candlestick.update_yaxes(title="Price", autorange=True)
graph_candlestick.update_layout(
title = aSymbolName,
height = 600,
width = 900,
showlegend = True)
graph_candlestick.update_layout(xaxis_rangebreaks = [ dict(bounds=["sat", "mon"]) ])
app = dash.Dash()
app.layout = html.Div(
html.Div([
dcc.Graph(id='graph_candlestick',figure=graph_candlestick)
])
)
#Server side implementation (slow)
@app.callback(
Output('graph_candlestick','figure'),
[Input('graph_candlestick','relayoutData')],[State('graph_candlestick', 'figure')]
)
def update_result(relOut,Fig):
if relOut == None:
return Fig
## if you don't use the rangeslider to adjust the plot, then relOut.keys() won't include the key xaxis.range
elif "xaxis.range" not in relOut.keys():
newLayout = go.Layout(
title=aSymbolName,
height=600,
width=800,
showlegend=True,
yaxis=dict(autorange=True),
template="plotly"
)
Fig['layout']=newLayout
return Fig
else:
ymin = df_ohlc.loc[df_ohlc['Date'].between(relOut['xaxis.range'][0], relOut['xaxis.range'][1]),'Low'].min()
ymax = df_ohlc.loc[df_ohlc['Date'].between(relOut['xaxis.range'][0], relOut['xaxis.range'][1]),'High'].max()
newLayout = go.Layout(
title=aSymbolName,
height=600,
width=800,
showlegend=True,
xaxis=dict(
rangeslider_visible=True,
range=relOut['xaxis.range']
),
yaxis=dict(range=[ymin,ymax]),
template="plotly"
)
Fig['layout']=newLayout
return Fig
app.run_server(debug=True)
graphs:CGraphs = CGraphs()
graphs.makeCandlestick("MSFT")
尽管已经给出了答案,但还是转向散景。由于使用 Bokeh 对没有经验的人来说也是 non-trivial,所以这就是我实现的。我希望它对以后的人有所帮助。
Python version : 3.8.10 (default, Nov 26 2021, 20:14:08)
IPython version : 8.0.1
Tornado version : 6.1
Bokeh version : 2.4.2
BokehJS static path : /home/.../.local/lib/python3.8/site-packages/bokeh/server/static
node.js version : (not installed)
npm version : (not installed)
Operating system : Linux-5.13.0-27-generic-x86_64-with-glibc2.29
结果:
实际代码:
def CandleStickBokeh(self, aSymbolName:str, aDataFrameOhlc = pd.DataFrame()):
# https://docs.bokeh.org/en/2.4.0/docs/gallery/candlestick.html
if 0 == aDataFrameOhlc.size:
downloader : CDownloader = CDownloader()
df_ohlc = downloader.GetHistoricalData(aSymbolName)
else:
df_ohlc = aDataFrameOhlc
df_ohlc['to_datetime'] = pd.to_datetime(df_ohlc['Date'])
inc = df_ohlc['Close'] > df_ohlc['Open']
dec = df_ohlc['Close'] < df_ohlc['Open']
w = 12*60*60*1000 # half day in ms
tools = "pan,wheel_zoom,box_zoom,reset,save"
figure_bokeh:bokeh.plotting.figure.Figure = figure(x_axis_type="datetime", tools=tools, width=1000, title = aSymbolName + " Candlestick")
figure_bokeh.xaxis.major_label_orientation = pi/4
figure_bokeh.grid.grid_line_alpha=0.3
figure_bokeh.segment(df_ohlc['to_datetime'], df_ohlc['High'], df_ohlc['to_datetime'], df_ohlc['Low'], color="black")
figure_bokeh.vbar(df_ohlc['to_datetime'][inc], w, df_ohlc['Open'][inc], df_ohlc['Close'][inc], fill_color="#D5E1DD", line_color="black")
figure_bokeh.vbar(df_ohlc['to_datetime'][dec], w, df_ohlc['Open'][dec], df_ohlc['Close'][dec], fill_color="#F2583E", line_color="black")
source = ColumnDataSource(data=dict(date=df_ohlc['to_datetime'], high=df_ohlc.High, low=df_ohlc.Low))
slider = DateRangeSlider(value=(min(df_ohlc['to_datetime'].values), max(df_ohlc['to_datetime'].values)),
start=min(df_ohlc['to_datetime'].values), end=max(df_ohlc['to_datetime'].values), step=1)
# The args dict links the variables in the Javascript code to the properties ('models') of Bokeh GUI elements.
# Here, the DateTimeSlider 'slider' is cb_obj, so these properties are linked by Bokey already.
# The properties to be explicitly linked are the horizontal and vertical range of figure_bokeh.
# Note that the slider has start and end members, but these are fixed by the data in 'source'.
# The slider values that can be changed by mouse dragging are cb_obj.value[0] and cb_obj.value[1].
callback = CustomJS(args=dict(slider=slider, x_range=figure_bokeh.x_range, y_range=figure_bokeh.y_range, source=source), code='''
clearTimeout(window._autoscale_timeout);
// the model that triggered the callback is cb_obj.
// if (Math.floor(Math.random() * 10000) == 0)
// {
// // Debug code if your console does not work. Tune the value in the condition to your needs.
// // Put displays the values you program in; can also be used to enter values manually.
// var s = new Date(start).toLocaleDateString("en-US")
// let xyz = prompt("Banana s " + s + " date[i] " + date[i].toString() + " x_selected_range_start "
// + x_selected_range_start.toString() + " end " + end.toString() , "Default");
// }
var date = source.data.date,
low = source.data.low,
high = source.data.high,
start = cb_obj.start, // This looks like it is fixed and does not change with the slider.
end = cb_obj.end, // In milliseconds since epoch
x_selected_range_start = cb_obj.value[0], // In milliseconds since epoch
x_selected_range_end = cb_obj.value[1], // In milliseconds since epoch
y_min = Infinity,
y_max = -Infinity;
for (var i=0; i < date.length; ++i)
{
if (x_selected_range_start <= date[i] && date[i] <= x_selected_range_end)
{
y_max = Math.max(high[i], y_max);
y_min = Math.min(low[i], y_min);
}
}
var pad = (y_max - y_min) * .05;
window._autoscale_timeout = setTimeout(function()
{
x_range.start = x_selected_range_start
x_range.end = x_selected_range_end
y_range.start = y_min - pad;
y_range.end = y_max + pad;
}
);
''')
slider.js_on_change('value', callback)
layout = column(figure_bokeh, slider)
show(layout)
下面的代码制作带有范围滑块的烛台图。如果我使滑块变窄,我想放大垂直比例。这是怎么做到的?我希望它有某种设置,但我找不到。目前结果可能看起来像屏幕截图;显然不是最优的。垂直刻度的很大一部分未使用。如何解决?
import sys
import pandas as pd
import plotly.graph_objects as go
from datetime import datetime
from Downloader import CDownloader
# import matplotlib.dates as mdates # Styling dates
class CGraphs:
def Candlestick(self, aSymbolName:str):
# Warning, this function reads from disk, so it is slow.
print(sys._getframe().f_code.co_name, ": Started. aSymbolName ", aSymbolName)
downloader : CDownloader = CDownloader()
df_ohlc : pd.DataFrame = downloader.GetHistoricalData(aSymbolName)
print("df_ohlc", df_ohlc)
graph_candlestick = go.Figure()
candle = go.Candlestick(x = df_ohlc['Date'],
open = df_ohlc['Open'],
high = df_ohlc['High'],
low = df_ohlc['Low'],
close = df_ohlc['Close'],
name = "Candlestick " + aSymbolName)
graph_candlestick.add_trace(candle)
graph_candlestick.update_xaxes(title="Date", rangeslider_visible=True)
graph_candlestick.update_yaxes(title="Price", autorange=True)
graph_candlestick.update_layout(
title = aSymbolName,
height = 600,
width = 900,
showlegend = True)
graph_candlestick.update_layout(xaxis_rangebreaks = [ dict(bounds=["sat", "mon"]) ])
graph_candlestick.show()
print(sys._getframe().f_code.co_name, ": Finished. aSymbolName ", aSymbolName)
graphs:CGraphs = CGraphs()
graphs.Candlestick("MSFT")
此功能在 plotly-python
中不可用,目前是 Plotly 团队的 open issue。
我认为您可以在 plotly-dash
中构建此功能,因为此库支持回调。例如,使用 server-side 实现(@kkollsg 在 this forum 上的回答提供了很多帮助):
import dash
from dash import Output, Input, State, dcc, html
import plotly.graph_objs as go
import numpy as np
import pandas as pd
import datetime
class CGraphs:
def makeCandlestick(self, aSymbolName:str):
# Warning, this function reads from disk, so it is slow.
# print(sys._getframe().f_code.co_name, ": Started. aSymbolName ", aSymbolName)
# downloader : CDownloader = CDownloader()
# df_ohlc : pd.DataFrame = downloader.GetHistoricalData(aSymbolName)
## load some similar stock data
df_ohlc = pd.read_csv('https://raw.githubusercontent.com/plotly/datasets/master/finance-charts-apple.csv')
df_ohlc.rename(columns=dict(zip(['AAPL.Open', 'AAPL.High', 'AAPL.Low', 'AAPL.Close'],['Open','High','Low','Close'])), inplace=True)
# print("loading data")
# print("df_ohlc", df_ohlc)
graph_candlestick = go.Figure()
candle = go.Candlestick(x = df_ohlc['Date'],
open = df_ohlc['Open'],
high = df_ohlc['High'],
low = df_ohlc['Low'],
close = df_ohlc['Close'],
name = "Candlestick " + aSymbolName)
graph_candlestick.add_trace(candle)
graph_candlestick.update_xaxes(title="Date", rangeslider_visible=True)
graph_candlestick.update_yaxes(title="Price", autorange=True)
graph_candlestick.update_layout(
title = aSymbolName,
height = 600,
width = 900,
showlegend = True)
graph_candlestick.update_layout(xaxis_rangebreaks = [ dict(bounds=["sat", "mon"]) ])
app = dash.Dash()
app.layout = html.Div(
html.Div([
dcc.Graph(id='graph_candlestick',figure=graph_candlestick)
])
)
#Server side implementation (slow)
@app.callback(
Output('graph_candlestick','figure'),
[Input('graph_candlestick','relayoutData')],[State('graph_candlestick', 'figure')]
)
def update_result(relOut,Fig):
if relOut == None:
return Fig
## if you don't use the rangeslider to adjust the plot, then relOut.keys() won't include the key xaxis.range
elif "xaxis.range" not in relOut.keys():
newLayout = go.Layout(
title=aSymbolName,
height=600,
width=800,
showlegend=True,
yaxis=dict(autorange=True),
template="plotly"
)
Fig['layout']=newLayout
return Fig
else:
ymin = df_ohlc.loc[df_ohlc['Date'].between(relOut['xaxis.range'][0], relOut['xaxis.range'][1]),'Low'].min()
ymax = df_ohlc.loc[df_ohlc['Date'].between(relOut['xaxis.range'][0], relOut['xaxis.range'][1]),'High'].max()
newLayout = go.Layout(
title=aSymbolName,
height=600,
width=800,
showlegend=True,
xaxis=dict(
rangeslider_visible=True,
range=relOut['xaxis.range']
),
yaxis=dict(range=[ymin,ymax]),
template="plotly"
)
Fig['layout']=newLayout
return Fig
app.run_server(debug=True)
graphs:CGraphs = CGraphs()
graphs.makeCandlestick("MSFT")
尽管已经给出了答案,但还是转向散景。由于使用 Bokeh 对没有经验的人来说也是 non-trivial,所以这就是我实现的。我希望它对以后的人有所帮助。
Python version : 3.8.10 (default, Nov 26 2021, 20:14:08)
IPython version : 8.0.1
Tornado version : 6.1
Bokeh version : 2.4.2
BokehJS static path : /home/.../.local/lib/python3.8/site-packages/bokeh/server/static
node.js version : (not installed)
npm version : (not installed)
Operating system : Linux-5.13.0-27-generic-x86_64-with-glibc2.29
结果:
实际代码:
def CandleStickBokeh(self, aSymbolName:str, aDataFrameOhlc = pd.DataFrame()):
# https://docs.bokeh.org/en/2.4.0/docs/gallery/candlestick.html
if 0 == aDataFrameOhlc.size:
downloader : CDownloader = CDownloader()
df_ohlc = downloader.GetHistoricalData(aSymbolName)
else:
df_ohlc = aDataFrameOhlc
df_ohlc['to_datetime'] = pd.to_datetime(df_ohlc['Date'])
inc = df_ohlc['Close'] > df_ohlc['Open']
dec = df_ohlc['Close'] < df_ohlc['Open']
w = 12*60*60*1000 # half day in ms
tools = "pan,wheel_zoom,box_zoom,reset,save"
figure_bokeh:bokeh.plotting.figure.Figure = figure(x_axis_type="datetime", tools=tools, width=1000, title = aSymbolName + " Candlestick")
figure_bokeh.xaxis.major_label_orientation = pi/4
figure_bokeh.grid.grid_line_alpha=0.3
figure_bokeh.segment(df_ohlc['to_datetime'], df_ohlc['High'], df_ohlc['to_datetime'], df_ohlc['Low'], color="black")
figure_bokeh.vbar(df_ohlc['to_datetime'][inc], w, df_ohlc['Open'][inc], df_ohlc['Close'][inc], fill_color="#D5E1DD", line_color="black")
figure_bokeh.vbar(df_ohlc['to_datetime'][dec], w, df_ohlc['Open'][dec], df_ohlc['Close'][dec], fill_color="#F2583E", line_color="black")
source = ColumnDataSource(data=dict(date=df_ohlc['to_datetime'], high=df_ohlc.High, low=df_ohlc.Low))
slider = DateRangeSlider(value=(min(df_ohlc['to_datetime'].values), max(df_ohlc['to_datetime'].values)),
start=min(df_ohlc['to_datetime'].values), end=max(df_ohlc['to_datetime'].values), step=1)
# The args dict links the variables in the Javascript code to the properties ('models') of Bokeh GUI elements.
# Here, the DateTimeSlider 'slider' is cb_obj, so these properties are linked by Bokey already.
# The properties to be explicitly linked are the horizontal and vertical range of figure_bokeh.
# Note that the slider has start and end members, but these are fixed by the data in 'source'.
# The slider values that can be changed by mouse dragging are cb_obj.value[0] and cb_obj.value[1].
callback = CustomJS(args=dict(slider=slider, x_range=figure_bokeh.x_range, y_range=figure_bokeh.y_range, source=source), code='''
clearTimeout(window._autoscale_timeout);
// the model that triggered the callback is cb_obj.
// if (Math.floor(Math.random() * 10000) == 0)
// {
// // Debug code if your console does not work. Tune the value in the condition to your needs.
// // Put displays the values you program in; can also be used to enter values manually.
// var s = new Date(start).toLocaleDateString("en-US")
// let xyz = prompt("Banana s " + s + " date[i] " + date[i].toString() + " x_selected_range_start "
// + x_selected_range_start.toString() + " end " + end.toString() , "Default");
// }
var date = source.data.date,
low = source.data.low,
high = source.data.high,
start = cb_obj.start, // This looks like it is fixed and does not change with the slider.
end = cb_obj.end, // In milliseconds since epoch
x_selected_range_start = cb_obj.value[0], // In milliseconds since epoch
x_selected_range_end = cb_obj.value[1], // In milliseconds since epoch
y_min = Infinity,
y_max = -Infinity;
for (var i=0; i < date.length; ++i)
{
if (x_selected_range_start <= date[i] && date[i] <= x_selected_range_end)
{
y_max = Math.max(high[i], y_max);
y_min = Math.min(low[i], y_min);
}
}
var pad = (y_max - y_min) * .05;
window._autoscale_timeout = setTimeout(function()
{
x_range.start = x_selected_range_start
x_range.end = x_selected_range_end
y_range.start = y_min - pad;
y_range.end = y_max + pad;
}
);
''')
slider.js_on_change('value', callback)
layout = column(figure_bokeh, slider)
show(layout)