在 PyQt5 中嵌入交互式 3D 图表

在 PyQt5 窗口中嵌入交互式 Matplotlib 3D 图表时,关键细节在于创建画布和 3D 坐标轴的顺序。

核心技巧是在添加 3D 坐标轴之前先创建 FigureCanvas。这样可以保留鼠标交互所需的事件连接。

相关的 Stack Overflow 笔记:

最小模式

canvas = FigureCanvas(fig)
ax = fig.add_subplot(111, projection='3d')

或者,将其封装在自定义画布类中:

class MyFigureCanvas(FigureCanvas):
    def __init__(self):
        self.figure = Figure()
        super(FigureCanvas, self).__init__(self.figure)
        self.axes = self.figure.add_subplot(111, projection='3d')

如果鼠标旋转、平移或缩放仍然不起作用,可以在画布创建完成后调用 ax.mouse_init()

fig = Figure()
canvas = FigureCanvas(fig)
ax = fig.add_subplot(111, projection='3d')
ax.mouse_init()

完整 PyQt5 示例

import sys

import numpy as np
from PyQt5.QtWidgets import QApplication, QMainWindow, QVBoxLayout, QWidget
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.figure import Figure
from mpl_toolkits.mplot3d import Axes3D  # noqa: F401; registers the 3D projection


class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()

        self.setWindowTitle('Interactive 3D Matplotlib Plot')

        fig = Figure()
        canvas = FigureCanvas(fig)
        ax = fig.add_subplot(111, projection='3d')

        x = np.linspace(-5, 5, 100)
        y = np.linspace(-5, 5, 100)
        x, y = np.meshgrid(x, y)
        z = np.sin(np.sqrt(x ** 2 + y ** 2))

        ax.plot_surface(x, y, z, cmap='viridis')
        ax.set_xlabel('X')
        ax.set_ylabel('Y')
        ax.set_zlabel('Z')
        ax.mouse_init()

        container = QWidget()
        layout = QVBoxLayout(container)
        layout.addWidget(canvas)
        self.setCentralWidget(container)


if __name__ == '__main__':
    app = QApplication(sys.argv)
    window = MainWindow()
    window.resize(900, 700)
    window.show()
    sys.exit(app.exec_())

图表应该可以用鼠标左键旋转,并可通过滚轮缩放。如果交互失败,首先检查画布是否在 add_subplot(..., projection='3d') 之前创建,然后用下面的代码确认已安装的 Matplotlib 后端:

import matplotlib
print(matplotlib.get_backend())

对于 PyQt5,后端通常应为 Qt5Agg 或其他与 Qt 兼容的后端。

Leave a Reply