What is the best way to embed an interactive 3D plot in a PySide GUI? I have looked at some examples on here of 2D plots embedded in a PySide GUI:
Getting PySide to Work With Matplotlib
Matplotlib Interactive Graph Embedded In PyQt
Python/Matplotlib/Pyside Fast Timetrace Scrolling
However, the functionality that I'm looking for is not quite the same. The figure needs to rotate and zoom based on mouse input from the user in the same way as if it were drawn in a separate window.
I'm trying to avoid having to go in manually and write functions for transforming mouse click + move into a figure rotate and canvas repaint--even if that's the only way, I'm not even sure how to do that. But I figure (no pun intended) that there should be a way to reuse the functionality already present for creating 3D plots in their own windows.
Here's my code. It works as intended, but the plot is not interactive. Any advice is appreciated!
EDIT: I fixed the use of FigureCanvas according to tcaswell's corrections. I also added a bit from the matplotlib Event Handling and Picking documentation to show that the figure seems to be getting the events upon mouseclick.
Final Edit: The following code now produces the plot as desired.
# -*- coding: utf-8 -*-
from PySide import QtCore, QtGui
import numpy as np
import matplotlib
import sys
# specify the use of PySide
matplotlib.rcParams['backend.qt4'] = "PySide"
# import the figure canvas for interfacing with the backend
from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg \
as FigureCanvas
# import 3D plotting
from mpl_toolkits.mplot3d import Axes3D # @UnusedImport
from matplotlib.figure import Figure
# Auto-generated code from QT Designer ----------------------------------------
class Ui_MainWindow(object):
def setupUi(self, MainWindow):
MainWindow.setObjectName("MainWindow")
MainWindow.resize(750, 497)
self.centralwidget = QtGui.QWidget(MainWindow)
self.centralwidget.setObjectName("centralwidget")
self.horizontalLayout_2 = QtGui.QHBoxLayout(self.centralwidget)
self.horizontalLayout_2.setObjectName("horizontalLayout_2")
self.frame_2 = QtGui.QFrame(self.centralwidget)
self.frame_2.setFrameShape(QtGui.QFrame.StyledPanel)
self.frame_2.setFrameShadow(QtGui.QFrame.Raised)
self.frame_2.setObjectName("frame_2")
self.verticalLayout = QtGui.QVBoxLayout(self.frame_2)
self.verticalLayout.setObjectName("verticalLayout")
self.label = QtGui.QLabel(self.frame_2)
self.label.setObjectName("label")
self.verticalLayout.addWidget(self.label)
self.label_2 = QtGui.QLabel(self.frame_2)
self.label_2.setObjectName("label_2")
self.verticalLayout.addWidget(self.label_2)
self.lineEdit = QtGui.QLineEdit(self.frame_2)
sizePolicy = QtGui.QSizePolicy(
QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Fixed)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(
self.lineEdit.sizePolicy().hasHeightForWidth())
self.lineEdit.setSizePolicy(sizePolicy)
self.lineEdit.setObjectName("lineEdit")
self.verticalLayout.addWidget(self.lineEdit)
spacerItem = QtGui.QSpacerItem(
20, 40, QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Expanding)
self.verticalLayout.addItem(spacerItem)
self.horizontalLayout_2.addWidget(self.frame_2)
self.frame_plot = QtGui.QFrame(self.centralwidget)
self.frame_plot.setMinimumSize(QtCore.QSize(500, 0))
self.frame_plot.setFrameShape(QtGui.QFrame.StyledPanel)
self.frame_plot.setFrameShadow(QtGui.QFrame.Raised)
self.frame_plot.setObjectName("frame_plot")
self.horizontalLayout_2.addWidget(self.frame_plot)
MainWindow.setCentralWidget(self.centralwidget)
self.retranslateUi(MainWindow)
QtCore.QMetaObject.connectSlotsByName(MainWindow)
def retranslateUi(self, MainWindow):
MainWindow.setWindowTitle(QtGui.QApplication.translate(
"MainWindow", "MainWindow", None, QtGui.QApplication.UnicodeUTF8))
self.label.setText(QtGui.QApplication.translate("MainWindow",
"This is a qlabel.", None, QtGui.QApplication.UnicodeUTF8))
self.label_2.setText(QtGui.QApplication.translate("MainWindow",
"And this is another one.", None, QtGui.QApplication.UnicodeUTF8))
self.lineEdit.setText(QtGui.QApplication.translate("MainWindow",
"Text goes here.", None, QtGui.QApplication.UnicodeUTF8))
# Auto-generated code from QT Designer ----------------------------------------
class MainWindow(QtGui.QMainWindow):
def __init__(self, parent=None):
super(MainWindow, self).__init__(parent)
# intialize the window
self.ui = Ui_MainWindow()
self.ui.setupUi(self)
# create the matplotlib widget and put it in the frame on the right
self.ui.plotWidget = Mpwidget(parent=self.ui.frame_plot)
class Mpwidget(FigureCanvas):
def __init__(self, parent=None):
self.figure = Figure(facecolor=(0, 0, 0))
super(Mpwidget, self).__init__(self.figure)
self.setParent(parent)
# plot random 3D data
self.axes = self.figure.add_subplot(111, projection='3d')
self.data = np.random.random((3, 100))
self.axes.plot(self.data[0, :], self.data[1, :], self.data[2, :])
if __name__ == "__main__":
app = QtGui.QApplication(sys.argv)
mw = MainWindow()
mw.show()
# adjust the frame size so that it fits right after the window is shown
s = mw.ui.frame_plot.size()
mw.ui.plotWidget.setGeometry(1, 1, s.width() - 2, s.height() - 2)
sys.exit(app.exec_())
To generate an interactive 3D plot first import the necessary packages and create a random dataset. Now using Axes3D(figure) function from the mplot3d library we can generate a required plot directly. Pass the data to the 3D plot and configure the title and labels.
First, we imported the pyplot submodule from Matplotlib. Then, we enabled 3D plots by importing the mplot3d submodule. After that, we created the Figure (fig) object by specifying the size in inches. It acts as the canvas on which we create the plot or Axes (ax) object.
You are not using FigureCanvas
right:
class Mpwidget(FigureCanvas):
def __init__(self, parent=None):
self.figure = Figure(facecolor=(0, 0, 0))
super(Mpwidget, self).__init__(self.figure) # this object _is_ your canvas
self.setParent(parent)
# plot random 3D data
self.axes = self.figure.add_subplot(111, projection='3d')
self.data = np.random.random((3, 100))
self.axes.plot(self.data[0, :], self.data[1, :], self.data[2, :])
if __name__ == "__main__":
app = QtGui.QApplication(sys.argv)
mw = MainWindow()
mw.show()
# adjust the frame size so that it fits right after the window is shown
s = mw.ui.frame_plot.size()
mw.ui.plotWidget.setGeometry(1, 1, s.width() - 2, s.height() - 2)
sys.exit(app.exec_())
Every time you called FigureCanvas(..)
you were attaching the figure to a new canvas (which were not the FigureCanvas
you were seeing) hence the call-backs were never firing (because they were listening on a FigureCanvas
that you couldn't see).
1) Create the FigureCanvas before adding the axes. See https://stackoverflow.com/a/9007892/3962328
canvas = FigureCanvas(fig)
ax = figure.add_subplot(111, projection='3d')
or
class MyFigureCanvas(FigureCanvas):
def __init__(self):
self.figure = Figure()
super(FigureCanvas, self).__init__(self.figure)
self.axes = self.figure.add_subplot(111, projection='3d')
2) Try ax.mouse_init() to restore the connection:
...
ax = fig.gca(projection="3d")
...
canvas = FigureCanvas(fig)
ax.mouse_init()
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With