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 互換バックエンドであるべきです。
