我有一个烛台图表,我正在用mplfinance显示在平移器中。我让mplfinance返回图形,以便我可以使用matplotlib来找到用户可能需要在图表上画线的x和y坐标。
我已经成功地使用底层画布在图表上画了线。我的想法是将这些行保存在数据库中,这样当用户返回到图表时,仍然会显示这些行。此外,用户还应该能够在返回图表后编辑或删除这些行。
我已经能够保存数据库中的行并检索它们。我的问题是,当我启动程序时,我无法让它们重新出现在画布上。该程序正在从数据库中检索线条,它似乎正在进行绘制线条的运动。不过,台词并没有出现。
使用几个打印语句,程序告诉我线已经画好了。我需要做什么才能让线条出现在画布上?下面是我的最小例子。
我没有包含用于在数据库中存储行的代码。在我的例子中,我要求程序绘制的线条没有出现。这是我唯一的问题。我遗漏了什么?
您可以找到我使用这里的csv文件,也可以使用任何具有特定股本的打开、高、低、关闭量信息的csv文件。任何帮助都将不胜感激。
from tkinter import *
import pandas as pd
import numpy as np
import datetime as dt
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
from matplotlib.widgets import MultiCursor
import mplfinance as mpf
from functools import partial
import math
class Example:
def __init__(self, figax, color='#0000FF', width=1):
"""
This class is used to draw on the canvas of a matplotlib chart.
:param: figax The figure axes object created by matplotlib
:param: color The color that should be used currently. The default
color is blue (#0000FF).
:param: width The width of the line stroke. The default is 1.
"""
self.fig, self.ax = figax
self.cur_ax = None
#bbox_height is total height of figure.
self.bbox_height = self.fig.canvas.figure.bbox.height
# bbox_width is total width of figure.
self.bbox_width = self.fig.canvas.figure.bbox.width
ax_len = len(self.ax)
# Create a list to hold the dimensions of the axes.
self.ax_dims = []
# Create a variable to hold the number of axes in the figure.
self.ax_ct = 0
self.ax_bounds = None
# Get the width and height of each axis in pixels.
for i in range(0, ax_len, 2):
self.ax_ct += 1
dims = self.ax[i].get_window_extent().transformed(self.fig.dpi_scale_trans.inverted())
awidth, aheight = dims.width, dims.height
# awidth is in pixels.
awidth *= self.fig.dpi
# aheight is in pixels.
aheight *= self.fig.dpi
d = {'Width': awidth, 'Height': aheight}
self.ax_dims.append(d)
self.ax_bounds = None
self.calc_axes_bounds()
# Set the ID of the object currently being drawn.
self.cur_id = None
self.color = color
self.width = width
self.draw_line()
def setColor(self, color):
self.color = color
def setWidth(self, width):
self.width = width
def calc_axes_bounds(self):
self.ax_bounds = []
# The first axis (ax[0]) will have a top y coordinate of 0.
heightTot = 0
# Calculate the bounding x, y coordinates for each axis.
for i in range(self.ax_ct):
# The x axis is shared by all plots; therefore, all axes
# will start and end at the same x mark.
x0 = 0
x1 = math.ceil(self.ax_dims[i]['Width'])
# Dealing with the top axis.
y0 = heightTot
y1 = self.ax_dims[i]['Height'] + y0
heightTot += y1
d = {'x0': x0, 'y0': y0, 'x1': x1, 'y1': y1}
self.ax_bounds.append(d)
def inaxes(self, x, y):
for i in range(len(self.ax_bounds)):
if (self.ax_bounds[i]['x0'] <= x <= self.ax_bounds[i]['x1']) and (self.ax_bounds[i]['y0'] <= y <= self.ax_bounds[i]['y1']):
self.cur_ax = i
ylim = self.ax[self.cur_ax].get_ylim()
def draw_line(self):
self.cur_ax = 0
self.cur_id = Line(self, 156, 39, 861, 273, self.color, self.width)
print("Done!")
class Line:
def __init__(self, parent, x0, y0, x1, y1, color, width):
self.parent = parent
self.ax = self.parent.ax
self.id = None
self.x0 = x0
self.y0 = y0
self.x1 = x1
self.y1 = y1
self.fig = self.parent.fig
self.color = color
self.width = width
#bbox_height is total height of figure.
self.bbox_height = self.fig.canvas.figure.bbox.height
# bbox_width is total width of figure.
self.bbox_width = self.fig.canvas.figure.bbox.width
# The current axis that is being worked with
self.cur_ax = self.parent.cur_ax
#print("Current axis is:", self.cur_ax)
#self.ax_bounds = self.parent.ax_bounds
self.id = None
self.draw()
def draw(self):
print("x0 is:", self.x0)
print("y0 is:", self.y0)
print("x1 is:", self.x1)
print("y1 is:", self.y1)
self.id = self.fig.canvas._tkcanvas.create_line(self.x0, self.y0, self.x1, self.y1, fill=self.color, width=self.width, activewidth=2, smooth=True)
print("ID is:", self.id)
def __str__(self):
return str(self.id)
if __name__ == '__main__':
dashboard = Tk()
dashboard.geometry("1200x700")
dashboard['bg'] = 'grey'
dashboard.title("Example Drawing Tools")
dashboard.state("zoomed") # Makes the window fully enlarged
# Opening data source
df = pd.read_csv("ATOS.csv", index_col=0, parse_dates=True)
dates = df.index.to_pydatetime().tolist()
# Create `marketcolors` to use with the `charles` style:
mc = mpf.make_marketcolors(up='#008000',down='#FF0000', vcdopcod=True, inherit=True)
# Create a new style based on `charles`.
sm_style = mpf.make_mpf_style(base_mpf_style='charles',
marketcolors=mc,
facecolor='#FFFFFF',
edgecolor='#999999',
figcolor='#FFFFFF'
)
figax = mpf.plot(df,
warn_too_much_data=6000,
panel_ratios=(3,1),
type="candle",
volume=True,
figsize=(12, 7),
main_panel=0,
volume_panel=1,
num_panels=2,
tight_layout=True,
scale_padding={'left': 0.02, 'top': 0, 'right': 1.2, 'bottom': 0.5},
ylabel="",
style=sm_style,
returnfig=True
)
fig, ax = figax
vol_ax = ax[2]
vol_ax.set_xlabel("")
vol_ax.set_ylabel("")
canvasbar = FigureCanvasTkAgg(fig, master=dashboard)
cursor = MultiCursor(canvasbar, ax, horizOn=True, vertOn=True, linewidth=0.75, color='#000000')
canvasbar.draw()
examp = Example(figax)
canvasbar.get_tk_widget().grid(row=0, column=0, columnspan=5, padx=0, pady=(0,20))
btn1 = Button(dashboard, text="Exit", command=quit)
btn1.grid(row=0, column=6, padx=5, pady=10, sticky='n')
dashboard.mainloop()
编辑:
这是允许用户在屏幕上画一条线的函数。
def draw_trend_line(self, event):
#print("cur_draw_id is:", str(self.cur_draw_id))
#print("Begin draw_trend_line")
self.event = event
#print("Event (x,y) is:", self.event.x, self.event.y)
if self.cur_draw_id is not None:
self.remove()
xMin = math.ceil(self.ax_bounds[self.cur_ax]['x0'])
xMax = math.ceil(self.ax_bounds[self.cur_ax]['x1'])
yMin = math.ceil(self.ax_bounds[self.cur_ax]['y0'])
yMax = math.ceil(self.ax_bounds[self.cur_ax]['y1'])
#print("yMax is:", yMax)
if self.event.x >= xMax:
x0 = xMax
elif self.event.x <= xMin:
x0 = xMin
else:
x0 = self.event.x
if self.event.y >= yMax:
y0 = yMax
elif self.event.y <= yMin:
y0 = yMin
else:
y0 = self.event.y
# Starting Position
if self.x_start is None:
self.x_start = x0
else:
x0 = self.x_start
if self.y_start is None:
self.y_start = y0
else:
y0 = self.y_start
# Ending Position
if self.event.x >= xMax:
x1 = xMax
elif self.event.x <= xMin:
x1 = xMin
else:
x1 = self.event.x
if self.event.y >= yMax:
y1 = yMax
elif self.event.y <= yMin:
y1 = yMin
else:
y1 = self.event.y
self.cur_draw_id = Line(self, x0, y0, x1, y1, self.color, self.width)
#print("End draw_trend_line")
当用户下次打开程序时,我希望能够复制用户绘制的行。我意识到我必须将行保存在数据库中,这是我没有问题的。我可以从数据库中检索这条线的坐标。程序就是不显示。
print语句显示,程序应该是在画线。我甚至尝试过强迫画布使用self.fig.canvas.draw()
重绘。
在draw_trend_line函数中,我有一个名为self.cur_ax
的变量。在我的整个程序中,我使用的是面板,所以可以有多个轴。请随便问你想让我详细说明的任何问题。
发布于 2022-03-28 17:36:20
这并不是真正的“答案”本身,但我有一些想法,可能有帮助,只是更容易写在这里,而不是作为一系列的评论。
我希望我能帮上更多忙,但我对平绒鞋不太熟悉。我希望看到工作和不工作的情况下的代码,然后我可能会发现一些东西。
下面是我的想法:我不明白为什么要直接在画布 (self.id = self.fig.canvas._tkcanvas.create_line(...)
)上创建一行,而matplotlib (和mplfinance)则在轴(而不是在画布/图形上)上画。
总的来说,在我看来,您所面临的问题似乎是与tkinter相关的问题,或者可能是tkinter/matplotlib问题:如果您可以使用一个非常简单的matplotlib示例(而不是mplfinance)来重现,那么可能更容易分离。
尽管如此,我要指出的是,mplfinance有绘制“任意”线条的能力 (和趋势线)。也许您可以更容易地使用alines
的mpf.plot()
kwarg,并在每次用户请求趋势线时简单地重新绘制这个情节。
最后,下面是我针对另一个mplfinance用户的问题而修改的一些代码。代码基本上能做你想做的事。此示例的数据来自mplfinance储存库中的mplfinance储存库目录。代码生成MACD图,然后使用matplotlib的Figure.ginput()
获取鼠标单击的位置。用户可以点击地块的主要部分,每两次鼠标点击就会在两次鼠标点击之间画出一条线。每一行都是通过在调用alines
kwarg规范中将两点添加到mpf.plot()
规范中的行列表中绘制的。
dill
编辑:编辑:我通过使用模拟包含用户绘制的行的数据库,对下面的代码做了一些调整。希望这至少会对你有所帮助,或者给你一些关于你如何使用tkinter做类似的事情的想法。
import pandas as pd
import mplfinance as mpf
import dill
import os
from matplotlib.widgets import MultiCursor
# read the data:
idf = pd.read_csv('../data/SPY_20110701_20120630_Bollinger.csv',
index_col=0,parse_dates=True)
df = idf.loc['2011-07-01':'2011-12-30',:]
# macd related calculations:
exp12 = df['Close'].ewm(span=12, adjust=False).mean()
exp26 = df['Close'].ewm(span=26, adjust=False).mean()
macd = exp12 - exp26
signal = macd.ewm(span=9, adjust=False).mean()
histogram = macd - signal
# initial plot:
apds = [mpf.make_addplot(exp12,color='lime'),
mpf.make_addplot(exp26,color='c'),
mpf.make_addplot(histogram,type='bar',width=0.7,panel=1,
color='dimgray',alpha=1,secondary_y=False),
mpf.make_addplot(macd,panel=1,color='fuchsia',secondary_y=True),
mpf.make_addplot(signal,panel=1,color='b',secondary_y=True),
]
# For some reason, which i have yet to determine, MultiCursor somehow
# causes ymin to be set to zero for the main candlestick Axes, but we
# can correct that problem by passing in specific values:
ymin = min(df['Low']) * 0.98
ymax = max(df['High']) * 1.02
# initial plot with cursor:
if os.path.exists('lines.dill'):
alines = dill.load(open('lines.dill','rb'))
else:
alines = []
fig, axlist = mpf.plot(df,type='candle',addplot=apds,figscale=1.25,
figratio=(8,6),title='\nMACD', ylim=(ymin,ymax),
alines=dict(alines=alines,colors='r'),
style='blueskies',volume=True,volume_panel=2,
panel_ratios=(6,3,2),returnfig=True)
multi = MultiCursor(fig.canvas, axlist[0:2], horizOn=True,
vertOn=True, color='pink', lw=1.2)
fig.canvas.draw_idle()
# ---------------------------------------------------
# set up an event loop where we wait for two
# mouse clicks, and then draw a line in between them,
# and then wait again for another two mouse clicks.
# This is a crude way to do it, but its quick and easy.
# Disadvantage is: user has 8 seconds to provide two clicks
# or the first click will be erased. But the 8 seconds
# repeats as long as the user does not close the Figure,
# so user can draw as many trend lines as they want.
# The advantage of doing it this way is we don't have
# to write all the mouse click handling stuff that's
# already written in `Figure.ginput()`.
not_closed = True
def on_close(event):
global not_closed
global alines
dill.dump(alines, open('lines.dill','wb'))
print('closing, please wait ...')
not_closed = False
fig.canvas.mpl_connect('close_event', on_close)
while not_closed:
vertices = fig.ginput(n=2,timeout=8)
if len(vertices) < 2:
continue
p1 = vertices[0]
p2 = vertices[1]
d1 = df.index[ round(p1[0]) ]
d2 = df.index[ round(p2[0]) ]
alines.append( [ (d1,p1[1]), (d2,p2[1]) ] )
apds = [mpf.make_addplot(exp12,color='lime',ax=axlist[0]),
mpf.make_addplot(exp26,color='c',ax=axlist[0]),
mpf.make_addplot(histogram,type='bar',width=0.7,panel=1,
ax=axlist[2],color='dimgray',alpha=1),
mpf.make_addplot(macd,panel=1,color='fuchsia',ax=axlist[3]),
mpf.make_addplot(signal,panel=1,color='b',ax=axlist[3])
]
mpf.plot(df,ax=axlist[0],type='candle',addplot=apds,ylim=(ymin,ymax),
alines=dict(alines=alines,colors='r'),
style='blueskies',volume=axlist[4],volume_panel=2,
panel_ratios=(6,3,2))
fig.canvas.draw_idle()
发布于 2022-04-01 22:32:38
为了防止其他人遇到我遇到的类似问题,我想在查看堆栈溢出的其他帖子之后,发布我想出的解决方案。其实很简单。我在dashboard.mainloop()
上加了一行。这一行是dashboard.after(20, drawInitialized.draw_line)
。我还删除了示例类中对self.draw_line()
的调用。每次启动应用程序时,它都会显示行。显然,这条线的坐标可以存储在数据库中,或者正如Daniel先生所建议的那样,可以使用'dill‘模块。
https://stackoverflow.com/questions/71594452
复制相似问题