When embedding an interactive Matplotlib 3D plot in a PyQt5 window, the important detail is the order in which the canvas and 3D axes are created.
The key trick is to create the FigureCanvas before adding the 3D axes. This preserves the event connections needed for mouse interaction.
Related Stack Overflow notes:
Table of Contents
Minimal pattern
canvas = FigureCanvas(fig)
ax = fig.add_subplot(111, projection='3d')
or, wrapped in a custom canvas class:
class MyFigureCanvas(FigureCanvas):
def __init__(self):
self.figure = Figure()
super(FigureCanvas, self).__init__(self.figure)
self.axes = self.figure.add_subplot(111, projection='3d')
If mouse rotation, panning, or zooming still does not work, call ax.mouse_init() after the canvas has been created:
fig = Figure()
canvas = FigureCanvas(fig)
ax = fig.add_subplot(111, projection='3d')
ax.mouse_init()
Complete PyQt5 example
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_())
The plot should rotate with the left mouse button and zoom with the scroll wheel. If interaction fails, first check that the canvas is created before add_subplot(..., projection='3d'), then verify the installed Matplotlib backend with:
import matplotlib
print(matplotlib.get_backend())
For PyQt5, the backend should normally be Qt5Agg or another Qt-compatible backend.
