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