【问题标题】:Problem updating a matplotlib chart from pyqt5 button从 pyqt5 按钮更新 matplotlib 图表时出现问题
【发布时间】:2020-02-24 20:12:07
【问题描述】:

我复制了Include matplotlib in pyqt5 with hover labels 的示例并放置了@ImportanceOfBeingErnest 建议的修改。我有使用通知的示例应用程序。完整代码在底部

但是,我现在想通过按钮或菜单项修改绘图的内容。为简单起见,我创建了一个菜单并添加了操作并定义了函数 updatePlot。我还尝试使用另一个函数 updatePlot2 再次调用修改后的 get_canvas 函数。

def updatePlot(self):
    print('executing update code')
    self.canvas.figure.axes.clear()
    x = np.linspace(0.1, 2*pi, 20)
    y = 3*cos(x)
    self.canvas.figure.axes.plot(x, y, color='blue', marker='o', linestyle='dashed',linewidth=2, markersize=8)

def updatePlot2(self):
    print('executing update code')
    self.canvas.figure.axes.clear()
    x = np.linspace(0.1, 2*pi, 20)
    y = 3*cos(x)
    self.canvas = self.get_canvas2(2)

def get_canvas2(self,n):
    fig, ax = plt.subplots()
    ax.tick_params(labeltop=False, labelright=True)
    x = np.linspace(0.1, 2*pi, 20)
    y = n*cos(x)
    ax.plot(x, y, color='blue', marker='o', linestyle='dashed',linewidth=2, markersize=8)
    canvas = FigureCanvas(fig)
    self.cursor = FollowDotCursor(ax, x, y, tolerance=20)
    return canvas

无论我做什么,我都无法让它发挥作用。它给了我诸如“列表对象没有属性图”或没有轴属性之类的错误。如果我使用 updatePlot2,我会在控制台 (spyder) 中获得正确的输出,但不会在 UI 中更新。

提前感谢您的帮助。完整代码如下:

import matplotlib.pyplot as plt import scipy.spatial as spatial import numpy as np from PyQt5.QtCore import * from PyQt5.QtGui import * from PyQt5.QtWidgets import * from PyQt5.QtWidgets import (QApplication, QMainWindow, QTextEdit, QDockWidget, QListWidget) from PyQt5.QtCore import Qt from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas from matplotlib.backends.backend_qt5agg import (NavigationToolbar2QT as NavigationToolbar) import sys

pi = np.pi cos = np.cos


def fmt(x, y):
    return 'Date: {x:0.2f}\nValue: {y:0.2f}\nDuration: {x:0.2f}'.format(x=x, y=y)

class FollowDotCursor(object):
    """Display the x,y location of the nearest data point.
    https://stackoverflow.com/a/4674445/190597 (Joe Kington)
    https://stackoverflow.com/a/13306887/190597 (unutbu)
    https://stackoverflow.com/a/15454427/190597 (unutbu)
    """
    def __init__(self, ax, x, y, tolerance=5, formatter=fmt, offsets=(-20, 20)):
        try:
            x = np.asarray(x, dtype='float')
        except (TypeError, ValueError):
            x = np.asarray(mdates.date2num(x), dtype='float')
        y = np.asarray(y, dtype='float')
        mask = ~(np.isnan(x) | np.isnan(y))
        x = x[mask]
        y = y[mask]
        self._points = np.column_stack((x, y))
        self.offsets = offsets
        y = y[np.abs(y-y.mean()) <= 3*y.std()]
        self.scale = x.ptp()
        self.scale = y.ptp() / self.scale if self.scale else 1
        self.tree = spatial.cKDTree(self.scaled(self._points))
        self.formatter = formatter
        self.tolerance = tolerance
        self.ax = ax
        self.fig = ax.figure
        self.ax.xaxis.set_label_position('top')
        self.dot = ax.scatter(
            [x.min()], [y.min()], s=180, color='green', alpha=0.7)
        self.annotation = self.setup_annotation()
        self.cid = self.fig.canvas.mpl_connect('motion_notify_event', self)

    def scaled(self, points):
        points = np.asarray(points)
        return points * (self.scale, 1)

    def __call__(self, event):
        ax = self.ax
        # event.inaxes is always the current axis. If you use twinx, ax could be
        # a different axis.
        if event.inaxes == ax:
            x, y = event.xdata, event.ydata
        elif event.inaxes is None:
            return
        else:
            inv = ax.transData.inverted()
            x, y = inv.transform([(event.x, event.y)]).ravel()
        annotation = self.annotation
        x, y = self.snap(x, y)
        annotation.xy = x, y
        annotation.set_text(self.formatter(x, y))
        self.dot.set_offsets((x, y))
        bbox = ax.viewLim
        event.canvas.draw()

    def setup_annotation(self):
        """Draw and hide the annotation box."""
        annotation = self.ax.annotate('', xy=(0, 0), ha = 'right', xytext = self.offsets, textcoords = 'offset points', va = 'bottom', bbox = dict(boxstyle='round,pad=0.5', fc='yellow', alpha=0.75),arrowprops = dict(arrowstyle='->', connectionstyle='arc3,rad=0'))
        return annotation

    def snap(self, x, y):
        """Return the value in self.tree closest to x, y."""
        dist, idx = self.tree.query(self.scaled((x, y)), k=1, p=1)
        try:
            return self._points[idx]
        except IndexError:
            # IndexError: index out of bounds
            return self._points[0]


class MainWindow(QMainWindow):

    def __init__(self):
        super().__init__()
        self.title = 'Testing'
        self.setWindowTitle(self.title)
        self.width = 1000
        self.height = 800
        self.setGeometry(10, 30, self.width, self.height)

        self.statusBar().showMessage('Ready')

        # Create menu and items
        mainMenu = self.menuBar()
        mainMenu.setNativeMenuBar(False)
        fileMenu = mainMenu.addMenu('Sources')
        # Update Plot
        btnUpdate = QAction(QIcon(''), 'Update Chart', self)
        btnUpdate.setShortcut('Ctrl+U')
        btnUpdate.setStatusTip('Clic to update chart.')
        btnUpdate.triggered.connect(self.updatePlot)
        fileMenu.addAction(btnUpdate)

        fileMenu.addSeparator()

        exitButton = QAction(QIcon('exit24.png'), 'Exit', self)
        exitButton.setShortcut('Ctrl+Q')
        exitButton.setStatusTip('Exit application')
        exitButton.triggered.connect(self.close)
        fileMenu.addAction(exitButton)

        self.canvas = self.get_canvas()
        self.toolbar = NavigationToolbar(self.canvas, self)


        w = QWidget()
        w.layout = QVBoxLayout()
        w.layout.addWidget(self.canvas)
        w.layout.addWidget(self.toolbar)
        w.setLayout(w.layout)

        self.setCentralWidget(w)
        # show maximize     
        self.showMaximized()

    def updatePlot(self):
        print('executing update code')
        self.canvas.figure.axes.clear()
        x = np.linspace(0.1, 2*pi, 20)
        y = 3*cos(x)
        self.canvas.figure.axes.plot(x, y, color='blue', marker='o', linestyle='dashed',linewidth=2, markersize=8)

    def updatePlot2(self):
        print('executing update code')
        self.canvas.figure.axes.clear()
        x = np.linspace(0.1, 2*pi, 20)
        y = 3*cos(x)
        self.canvas = self.get_canvas2(2)

    def get_canvas2(self,n):
        fig, ax = plt.subplots()
        ax.tick_params(labeltop=False, labelright=True)
        x = np.linspace(0.1, 2*pi, 20)
        y = n*cos(x)
        ax.plot(x, y, color='blue', marker='o', linestyle='dashed',linewidth=2, markersize=8)



        canvas = FigureCanvas(fig)

        self.cursor = FollowDotCursor(ax, x, y, tolerance=20)
        return canvas

    def get_canvas(self):
        fig, ax = plt.subplots()
        ax.tick_params(labeltop=False, labelright=True)
        x = np.linspace(0.1, 2*pi, 20)
        y = cos(x)
        ax.plot(x, y, color='blue', marker='o', linestyle='dashed',linewidth=2, markersize=8)



        canvas = FigureCanvas(fig)

        self.cursor = FollowDotCursor(ax, x, y, tolerance=20)
        return canvas

app = QApplication(sys.argv) win = MainWindow() sys.exit(app.exec_())

【问题讨论】:

    标签: python matplotlib pyqt pyqt5


    【解决方案1】:

    如何指示错误:

    AttributeError: 'list' object has no attribute 'plot'
    

    axes 是一个包含多个 AxesSubplot 的列表,因为使用 plt.subplots() 您可以生成多个轴,因此在您的情况下,解决方案是使用第一个(并且仅存在一个):

    self.canvas.figure.<b>axes[0]</b>.plot(...)

    【讨论】:

    • 嗨,非常感谢。这是一个愚蠢的错误,但你救了我几个小时!
    • 谢谢,不知道!
    • 顺便说一句,我注意到点击更新后,情节不会刷新,直到我将鼠标悬停在其中...我使用 qtcreator 进行了另一个实现,并在点击按钮后立即刷新(但没有注释,这就是我选择这个解决方案的原因)。您对为什么会发生有任何想法吗?
    • @Juano 这是另一个问题,所以你应该在新帖子中创建另一个问题
    • 为了记录,我通过在更新函数中调用 self.canvas.draw() 解决了不刷新问题!
    猜你喜欢
    • 2023-03-23
    • 1970-01-01
    • 2015-05-16
    • 2019-09-25
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-10-01
    • 1970-01-01
    相关资源
    最近更新 更多