I'm currently working on a project where I want to embed a matplotlib plot into a pyqt5 GUI. The plot is interactive and allows for the drawing of shaded rectangles.
The problem is that the plot is not interactive when it is embedded in the pyqt window. When I run the program, line 147 in the below code (plt.show() in the mplWidget class) displays the matplotlib plot, and I can then draw a rectangle as you can see here:
However, when this window closes and the plot is embedded in the pyqt window, it becomes uneditable
I want the GUI plot to function as the matplotlib figure does.
I know that this has to do with the fact that I have to provide the pyqt functionality with connect()
statements, but I don't know where those go / how they would fit in to this program.
I don't know how to connect to a matplotlib. Do I just use connect()
statements to the mplWidget class functions?
Any help is appreciated!
(I realize that I will need to take line 147 out (plt.show()) so that the figure frame does not pop up before the gui, but I just had it temporarily to show that the mpl class still functions as intended, and the problem is that it becomes "static" upon embedding)
import numpy as np
import matplotlib.pyplot as plt
from PyQt5 import QtCore, QtGui, QtWidgets
from matplotlib.backends.backend_qt5agg import (FigureCanvasQTAgg, NavigationToolbar2QT as NavigationToolbar)
class topLevelWindow(QtWidgets.QMainWindow):
def __init__(self):
super().__init__()
# Add a central widget to the main window and lay it out in a grid
self.centralwidget = QtWidgets.QWidget(self)
self.centralwidget.setObjectName("centralwidget")
self.gridLayout_5 = QtWidgets.QGridLayout(self.centralwidget)
self.gridLayout_5.setObjectName("gridLayout_5")
# Create mainTabs object as well as the displayFrame and displaySettingsFrame that go in the central widget
# Display Frame and Display settings frames
self.displayFrame = QtWidgets.QFrame(self.centralwidget)
self.verticalLayout_22 = QtWidgets.QVBoxLayout(self.displayFrame)
self.verticalLayout_22.setObjectName("verticalLayout_22")
self.gridLayout_5.addWidget(self.displayFrame, 1, 0, 1, 1)
self.devConstructor = mplWidget()
self.dynamic_canvas = FigureCanvasQTAgg(self.devConstructor.fig)
self.verticalLayout_22.addWidget(self.dynamic_canvas)
self._dynamic_ax = self.devConstructor.ax
self.setCentralWidget(self.centralwidget)
# Perform final windows setup (set buddies, translate, tab order, initial tabs, etc)
_translate = QtCore.QCoreApplication.translate
self.setWindowTitle(_translate("MainWindow", "MainWindow")) # Was self.setWindowTitle
QtCore.QMetaObject.connectSlotsByName(self)
self.show()
class mplWidget(QtWidgets.QWidget): # n.b. changed this from Object to QWidget and added a super()
def setSnapBase(self, base):
return lambda value: int(base*round(float(value)/base))
def onclick(self, event):
if self.plotSnap is False:
self.bottomLeftX = event.xdata
self.bottomLeftY = event.ydata
else:
self.calculateSnapCoordinates = self.setSnapBase(self.plotSnap)
self.bottomLeftX = self.calculateSnapCoordinates(event.xdata)
self.bottomLeftY = self.calculateSnapCoordinates(event.ydata)
try:
self.aspan.remove()
except:
pass
self.moving = True
def onrelease(self, event):
if self.plotSnap is False:
self.topRightX = event.xdata
self.topRightY = event.ydata
else:
try:
calculateSnapCoordinates = self.setSnapBase(self.plotSnap)
self.topRightX = calculateSnapCoordinates(event.xdata)
self.topRightY = calculateSnapCoordinates(event.ydata)
except:
pass
self.x = np.array([self.bottomLeftX, self.bottomLeftX, self.topRightX, self.topRightX, self.bottomLeftX])
self.y = np.array([self.bottomLeftY, self.topRightY, self.topRightY, self.bottomLeftY, self.bottomLeftY])
self.myPlot.set_xdata(self.x)
self.myPlot.set_ydata(self.y)
# ax.fill_between(x, y, color=defaultColors[0, :], alpha=.25)
ylimDiff = self.ax.get_ylim()[1] - self.ax.get_ylim()[0]
self.aspan = self.ax.axvspan(self.bottomLeftX, self.topRightX,
(self.bottomLeftY-self.ax.get_ylim()[0])/ylimDiff,
(self.topRightY-self.ax.get_ylim()[0])/ylimDiff,
color=self.defaultColors[0, :], alpha=.25)
self.moving = False
self.fig.canvas.draw()
def onmotion(self, event):
if self.moving is False:
return
if event.inaxes is None:
return
if event.button != 1:
return
if self.plotSnap is False:
self.topRightX = event.xdata
self.topRightY = event.ydata
else:
self.calculateSnapCoordinates = self.setSnapBase(self.plotSnap)
self.topRightX = self.calculateSnapCoordinates(event.xdata)
self.topRightY = self.calculateSnapCoordinates(event.ydata)
self.x = np.array([self.bottomLeftX, self.bottomLeftX, self.topRightX, self.topRightX, self.bottomLeftX])
self.y = np.array([self.bottomLeftY, self.topRightY, self.topRightY, self.bottomLeftY, self.bottomLeftY])
self.myPlot.set_xdata(self.x)
self.myPlot.set_ydata(self.y)
self.fig.canvas.draw()
def __init__(self):
super(mplWidget, self).__init__()
# Set default colors array
self.defaultColors = np.array([[0, 0.4470, 0.7410], [0.8500, 0.3250, 0.0980], [0.9290, 0.6940, 0.1250],
[0.4660, 0.6740, 0.1880], [0.6350, 0.0780, 0.1840], [0.4940, 0.1840, 0.5560],
[0.3010, 0.7450, 0.9330]])
# Create a figure with axes
self.fig = plt.figure()
self.ax = self.fig.gca()
# Form the plot and shading
self.bottomLeftX = 0; self.bottomLeftY = 0; self.topRightX = 0; self.topRightY = 0
self.x = np.array([self.bottomLeftX, self.bottomLeftX, self.topRightX, self.topRightX, self.bottomLeftX])
self.y = np.array([self.bottomLeftY, self.topRightY, self.topRightY, self.bottomLeftY, self.bottomLeftY])
self.myPlot, = self.ax.plot(self.x, self.y, color=self.defaultColors[0, :])
self.aspan = self.ax.axvspan(self.bottomLeftX, self.topRightX, color= self.defaultColors[0, :], alpha=0)
# Set moving flag false (determines if mouse is being clicked and dragged inside plot). Set graph snap
self.moving = False
self.plotSnap = 5
# Set up connectivity
self.cid = self.fig.canvas.mpl_connect('button_press_event', self.onclick)
self.cid = self.fig.canvas.mpl_connect('button_release_event', self.onrelease)
self.cid = self.fig.canvas.mpl_connect('motion_notify_event', self.onmotion)
# Set plot limits and show it
plt.ylim((-100, 100))
plt.xlim((-100, 100))
plt.show()
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
MainWindow = topLevelWindow()
sys.exit(app.exec_())
In your code there are many so I will only list them:
if self.plotSnap is False:
, just if not self.plotSnap:
also I see it illogical to think that "plotSnap" is False, if you want to disable then set an impossible value, such as 0 or negative.Considering the above I have made MplWidget inherit from FigureCanvasQTAgg, I have eliminated the use of pyplot:
import numpy as np
from PyQt5 import QtCore, QtGui, QtWidgets
from matplotlib.backends.backend_qt5agg import (
FigureCanvasQTAgg,
NavigationToolbar2QT as NavigationToolbar,
)
from matplotlib.figure import Figure
class MplWidget(FigureCanvasQTAgg):
def __init__(self, parent=None):
fig = Figure()
super(MplWidget, self).__init__(fig)
self.setParent(parent)
# Set default colors array
self.defaultColors = np.array(
[
[0, 0.4470, 0.7410],
[0.8500, 0.3250, 0.0980],
[0.9290, 0.6940, 0.1250],
[0.4660, 0.6740, 0.1880],
[0.6350, 0.0780, 0.1840],
[0.4940, 0.1840, 0.5560],
[0.3010, 0.7450, 0.9330],
]
)
# Create a figure with axes
self.ax = self.figure.add_subplot(111)
# Form the plot and shading
self.bottomLeftX = 0
self.bottomLeftY = 0
self.topRightX = 0
self.topRightY = 0
self.x = np.array(
[
self.bottomLeftX,
self.bottomLeftX,
self.topRightX,
self.topRightX,
self.bottomLeftX,
]
)
self.y = np.array(
[
self.bottomLeftY,
self.topRightY,
self.topRightY,
self.bottomLeftY,
self.bottomLeftY,
]
)
(self.myPlot,) = self.ax.plot(self.x, self.y, color=self.defaultColors[0, :])
self.aspan = self.ax.axvspan(
self.bottomLeftX, self.topRightX, color=self.defaultColors[0, :], alpha=0
)
self.ax.set_xlim((-100, 100))
self.ax.set_ylim((-100, 100))
# Set moving flag false (determines if mouse is being clicked and dragged inside plot). Set graph snap
self.moving = False
self.plotSnap = 5
# Set up connectivity
self.cid1 = self.mpl_connect("button_press_event", self.onclick)
self.cid2 = self.mpl_connect("button_release_event", self.onrelease)
self.cid3 = self.mpl_connect("motion_notify_event", self.onmotion)
def setSnapBase(self, base):
return lambda value: int(base * round(float(value) / base))
def onclick(self, event):
if self.plotSnap <= 0:
self.bottomLeftX = event.xdata
self.bottomLeftY = event.ydata
else:
self.calculateSnapCoordinates = self.setSnapBase(self.plotSnap)
self.bottomLeftX = self.calculateSnapCoordinates(event.xdata)
self.bottomLeftY = self.calculateSnapCoordinates(event.ydata)
try:
self.aspan.remove()
except:
pass
self.moving = True
def onrelease(self, event):
if self.plotSnap <= 0:
self.topRightX = event.xdata
self.topRightY = event.ydata
else:
try:
calculateSnapCoordinates = self.setSnapBase(self.plotSnap)
self.topRightX = calculateSnapCoordinates(event.xdata)
self.topRightY = calculateSnapCoordinates(event.ydata)
except:
pass
self.x = np.array(
[
self.bottomLeftX,
self.bottomLeftX,
self.topRightX,
self.topRightX,
self.bottomLeftX,
]
)
self.y = np.array(
[
self.bottomLeftY,
self.topRightY,
self.topRightY,
self.bottomLeftY,
self.bottomLeftY,
]
)
self.myPlot.set_xdata(self.x)
self.myPlot.set_ydata(self.y)
# ax.fill_between(x, y, color=defaultColors[0, :], alpha=.25)
ylimDiff = self.ax.get_ylim()[1] - self.ax.get_ylim()[0]
self.aspan = self.ax.axvspan(
self.bottomLeftX,
self.topRightX,
(self.bottomLeftY - self.ax.get_ylim()[0]) / ylimDiff,
(self.topRightY - self.ax.get_ylim()[0]) / ylimDiff,
color=self.defaultColors[0, :],
alpha=0.25,
)
self.moving = False
self.draw()
def onmotion(self, event):
if not self.moving:
return
if event.inaxes is None:
return
if event.button != 1:
return
if self.plotSnap <= 0:
self.topRightX = event.xdata
self.topRightY = event.ydata
else:
self.calculateSnapCoordinates = self.setSnapBase(self.plotSnap)
self.topRightX = self.calculateSnapCoordinates(event.xdata)
self.topRightY = self.calculateSnapCoordinates(event.ydata)
self.x = np.array(
[
self.bottomLeftX,
self.bottomLeftX,
self.topRightX,
self.topRightX,
self.bottomLeftX,
]
)
self.y = np.array(
[
self.bottomLeftY,
self.topRightY,
self.topRightY,
self.bottomLeftY,
self.bottomLeftY,
]
)
self.myPlot.set_xdata(self.x)
self.myPlot.set_ydata(self.y)
self.draw()
class TopLevelWindow(QtWidgets.QMainWindow):
def __init__(self):
super().__init__()
self.canvas = MplWidget()
self.setCentralWidget(self.canvas)
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
w = TopLevelWindow()
w.show()
sys.exit(app.exec_())