【问题标题】:Matplotlib callbacks don't work when matplotlib.FigureCanvas is embedded in a PyQt5 application?当 matplotlib.FigureCanvas 嵌入 PyQt5 应用程序时,Matplotlib 回调不起作用?
【发布时间】:2020-10-13 22:56:45
【问题描述】:

请您,StackOverflow 的干瘪长老们,

我正在尝试使用 PyQt5 制作一个小型 GUI 应用程序,其中 matplotlib.FigureCanvas 对象用作小部件来显示数据,我还希望通过使用 matplotlib 的回调函数来使其更具交互性。但是,这些回调似乎不适用于 PyQt5 窗口中嵌入的 FigureCanvas。

我认为实例化 Qt5 应用程序可能会干扰 matplotlib 的事件处理程序,但我不确定如何继续。有没有办法让 matplotlib 事件引发 Qt5 信号?我可以有两个单独的线程,两个事件处理程序都可以在其中运行吗?

下面的例子是我能简化的最简单的例子,它说明了这个问题。

案例 A:在 Matplotlib 窗口中运行

首先按原样运行,注意所需的行为:onclick 函数在弹出时在图中单击时被调用。 (还要注意第二个代码块,案例 B 不会在第一个之后运行)

案例 B:在 PyQt5 中运行

现在,注释掉Case A代码块,运行:点击图中不调用回调函数。

import sys
import numpy as np

from PyQt5 import QtCore, QtWidgets, uic

import matplotlib
matplotlib.use('QT5Agg')
import matplotlib.pylab as plt
from matplotlib.backends.backend_qt5agg import FigureCanvas
from matplotlib.backends.backend_qt5agg import NavigationToolbar2QT as NavigationToolbar
matplotlib.rcParams["toolbar"] = "toolmanager"

def onclick(event):
    '''example callback function'''
    print('got click: button=%d, x=%d, y=%d, xdata=%f, ydata=%f' %
          ( event.button, event.x, event.y, event.xdata, event.ydata))

class MPLWidget(QtWidgets.QWidget):
    '''
    Widget which should act like a matplotlib FigureCanvas, but embedded in PyQt5
    '''
    def __init__(self, parent=None, **kwargs):
        super().__init__(parent, **kwargs)
        
        # Making Matplotlib figure object with data
        fig, ax = plt.subplots()
        fig.suptitle("Plot in PyQt5: click and look in console")
        ax.plot(np.linspace(0,1,2))
        # Attaching matplotlib callback function which doesnt seem to work
        cid = fig.canvas.mpl_connect('button_press_event', onclick)

        self.layout = QtWidgets.QVBoxLayout(self)
        self.layout.setContentsMargins(0, 0, 0, 0)      
        self.plotWidget = FigureCanvas(fig)
        self.toolbar = NavigationToolbar(self.plotWidget, self)

        self.layout.addWidget(self.toolbar)
        self.layout.addWidget(self.plotWidget)


class TestWindow(QtWidgets.QMainWindow):
    '''Window which is a stand in for the larger PyQt5 application which needs a plot embedded'''
    def __init__(self):
        super().__init__()
        self.mplwidget = MPLWidget(self)
        self.setCentralWidget(self.mplwidget)

if __name__ == '__main__':

    # Case A: Using matplotlib without PyQt5, 
    # Note that the callback is triggered and something is printed when you click in the figure
    fig, ax = plt.subplots()
    fig.suptitle("Standalone matplotlib: click and look in console")
    ax.plot(np.linspace(0,1,2))
    cid = fig.canvas.mpl_connect('button_press_event', onclick)
    plt.show()
    
    # Case B:  Running PyQt5 + Matplotlib widget
    # Note that nothing is printed when you click in the figure
    app = QtWidgets.QApplication(sys.argv)
    window = TestWindow()
    window.show()
    sys.exit(app.exec_())

【问题讨论】:

    标签: python matplotlib pyqt5


    【解决方案1】:

    如果您要使用 pyqt5 中的 FigureCanvas 但使用 Figure() 构建,则不必使用“plt”,如 the official example 所示:

    import sys
    import numpy as np
    
    from PyQt5 import QtCore, QtWidgets
    
    import matplotlib
    
    matplotlib.use("QT5Agg")
    
    from matplotlib.backends.backend_qt5agg import FigureCanvas
    from matplotlib.backends.backend_qt5agg import NavigationToolbar2QT as NavigationToolbar
    
    matplotlib.rcParams["toolbar"] = "toolmanager"
    
    from matplotlib.figure import Figure
    
    
    def onclick(event):
        """example callback function"""
        print(
            "got click: button=%d, x=%d, y=%d, xdata=%f, ydata=%f"
            % (event.button, event.x, event.y, event.xdata, event.ydata)
        )
    
    
    class MPLWidget(QtWidgets.QWidget):
        """
        Widget which should act like a matplotlib FigureCanvas, but embedded in PyQt5
        """
    
        def __init__(self, parent=None, **kwargs):
            super().__init__(parent, **kwargs)
    
            # Making Matplotlib figure object with data
            self.canvas = FigureCanvas(Figure())
            ax = self.canvas.figure.add_subplot(111)
            ax.set_title("Plot in PyQt5: click and look in console")
            ax.plot(np.linspace(0, 1, 2))
    
            cid = self.canvas.mpl_connect("button_press_event", onclick)
    
            self.layout = QtWidgets.QVBoxLayout(self)
            self.layout.setContentsMargins(0, 0, 0, 0)
            self.toolbar = NavigationToolbar(self.canvas, self)
    
            self.layout.addWidget(self.toolbar)
            self.layout.addWidget(self.canvas)
    
    
    class TestWindow(QtWidgets.QMainWindow):
        """Window which is a stand in for the larger PyQt5 application which needs a plot embedded"""
    
        def __init__(self):
            super().__init__()
            self.mplwidget = MPLWidget(self)
            self.setCentralWidget(self.mplwidget)
    
    
    if __name__ == "__main__":
        app = QtWidgets.QApplication(sys.argv)
        window = TestWindow()
        window.show()
        sys.exit(app.exec_())
    

    【讨论】:

    • 哇,我从来没有找到 matplotlib 文档的那个部分。非常感谢您的回复,这非常有效!
    • @eyllanesc,嗨,我有一个类似的问题,关于如何将画布中斧头的“xlim_changed”信号连接到回调函数?我正在尝试进行一些下采样。
    猜你喜欢
    • 1970-01-01
    • 2021-12-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-11-02
    • 1970-01-01
    相关资源
    最近更新 更多