首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >问答首页 >tkinter画布没有用matplotlib图显示线

tkinter画布没有用matplotlib图显示线
EN

Stack Overflow用户
提问于 2022-03-23 21:47:14
回答 2查看 165关注 0票数 1

我有一个烛台图表,我正在用mplfinance显示在平移器中。我让mplfinance返回图形,以便我可以使用matplotlib来找到用户可能需要在图表上画线的x和y坐标。

我已经成功地使用底层画布在图表上画了线。我的想法是将这些行保存在数据库中,这样当用户返回到图表时,仍然会显示这些行。此外,用户还应该能够在返回图表后编辑或删除这些行。

我已经能够保存数据库中的行并检索它们。我的问题是,当我启动程序时,我无法让它们重新出现在画布上。该程序正在从数据库中检索线条,它似乎正在进行绘制线条的运动。不过,台词并没有出现。

使用几个打印语句,程序告诉我线已经画好了。我需要做什么才能让线条出现在画布上?下面是我的最小例子。

我没有包含用于在数据库中存储行的代码。在我的例子中,我要求程序绘制的线条没有出现。这是我唯一的问题。我遗漏了什么?

您可以找到我使用这里的csv文件,也可以使用任何具有特定股本的打开、高、低、关闭量信息的csv文件。任何帮助都将不胜感激。

代码语言:javascript
运行
复制
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()

编辑:

这是允许用户在屏幕上画一条线的函数。

代码语言:javascript
运行
复制
    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的变量。在我的整个程序中,我使用的是面板,所以可以有多个轴。请随便问你想让我详细说明的任何问题。

EN

回答 2

Stack Overflow用户

发布于 2022-03-28 17:36:20

这并不是真正的“答案”本身,但我有一些想法,可能有帮助,只是更容易写在这里,而不是作为一系列的评论。

我希望我能帮上更多忙,但我对平绒鞋不太熟悉。我希望看到工作和不工作的情况下的代码,然后我可能会发现一些东西。

下面是我的想法:我不明白为什么要直接在画布 (self.id = self.fig.canvas._tkcanvas.create_line(...))上创建一行,而matplotlib (和mplfinance)则在轴(而不是在画布/图形上)上画。

总的来说,在我看来,您所面临的问题似乎是与tkinter相关的问题,或者可能是tkinter/matplotlib问题:如果您可以使用一个非常简单的matplotlib示例(而不是mplfinance)来重现,那么可能更容易分离。

尽管如此,我要指出的是,mplfinance有绘制“任意”线条的能力 (和趋势线)。也许您可以更容易地使用alinesmpf.plot() kwarg,并在每次用户请求趋势线时简单地重新绘制这个情节。

最后,下面是我针对另一个mplfinance用户的问题而修改的一些代码。代码基本上能做你想做的事。此示例的数据来自mplfinance储存库中的mplfinance储存库目录。代码生成MACD图,然后使用matplotlib的Figure.ginput()获取鼠标单击的位置。用户可以点击地块的主要部分,每两次鼠标点击就会在两次鼠标点击之间画出一条线。每一行都是通过在调用alines kwarg规范中将两点添加到mpf.plot()规范中的行列表中绘制的。

dill编辑:编辑:我通过使用模拟包含用户绘制的行的数据库,对下面的代码做了一些调整。希望这至少会对你有所帮助,或者给你一些关于你如何使用tkinter做类似的事情的想法。

代码语言:javascript
运行
复制
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()
票数 0
EN

Stack Overflow用户

发布于 2022-04-01 22:32:38

为了防止其他人遇到我遇到的类似问题,我想在查看堆栈溢出的其他帖子之后,发布我想出的解决方案。其实很简单。我在dashboard.mainloop()上加了一行。这一行是dashboard.after(20, drawInitialized.draw_line)。我还删除了示例类中对self.draw_line()的调用。每次启动应用程序时,它都会显示行。显然,这条线的坐标可以存储在数据库中,或者正如Daniel先生所建议的那样,可以使用'dill‘模块。

票数 0
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/71594452

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档