Prithee, wizened elders of StackOverflow,
I am trying to make a small GUI application using PyQt5, with matplotlib.FigureCanvas objects used as widgets to display data which I also want to make more interactive through the use of matplotlib's callback functions. However, these callbacks don't appear to work for a FigureCanvas embedded in the PyQt5 window.
I'm thinking it's likely that instantiating the Qt5 application interferes with matplotlib's event handler, but I'm not sure how to proceed. Is there a way to make the matplotlib events raise Qt5 signals? Could I have two separate threads in which both event handlers can run?
The example below is the simplest thing I could pare down which illustrates the issue.
Case A: Running in Matplotlib Window
First run as-is noting the desired behavior: the onclick
function is called upon a click in the figure once it pops up. (also note that the second code block, Case B won't run after the first)
Case B: Running in PyQt5
Now, comment out the Case A code block, and run: clicking in the figure doesn't call the callback function.
import sys
import numpy as np
from PyQt5 import QtCore, QtWidgets, uic
import matplotlib
matplotlib.use('QT5Agg')
import matplotlib.pylab as plt
from matplotlib.backends.backend_qt5agg import FigureCanvas
from matplotlib.backends.backend_qt5agg import NavigationToolbar2QT as NavigationToolbar
matplotlib.rcParams["toolbar"] = "toolmanager"
def onclick(event):
'''example callback function'''
print('got click: button=%d, x=%d, y=%d, xdata=%f, ydata=%f' %
( event.button, event.x, event.y, event.xdata, event.ydata))
class MPLWidget(QtWidgets.QWidget):
'''
Widget which should act like a matplotlib FigureCanvas, but embedded in PyQt5
'''
def __init__(self, parent=None, **kwargs):
super().__init__(parent, **kwargs)
# Making Matplotlib figure object with data
fig, ax = plt.subplots()
fig.suptitle("Plot in PyQt5: click and look in console")
ax.plot(np.linspace(0,1,2))
# Attaching matplotlib callback function which doesnt seem to work
cid = fig.canvas.mpl_connect('button_press_event', onclick)
self.layout = QtWidgets.QVBoxLayout(self)
self.layout.setContentsMargins(0, 0, 0, 0)
self.plotWidget = FigureCanvas(fig)
self.toolbar = NavigationToolbar(self.plotWidget, self)
self.layout.addWidget(self.toolbar)
self.layout.addWidget(self.plotWidget)
class TestWindow(QtWidgets.QMainWindow):
'''Window which is a stand in for the larger PyQt5 application which needs a plot embedded'''
def __init__(self):
super().__init__()
self.mplwidget = MPLWidget(self)
self.setCentralWidget(self.mplwidget)
if __name__ == '__main__':
# Case A: Using matplotlib without PyQt5,
# Note that the callback is triggered and something is printed when you click in the figure
fig, ax = plt.subplots()
fig.suptitle("Standalone matplotlib: click and look in console")
ax.plot(np.linspace(0,1,2))
cid = fig.canvas.mpl_connect('button_press_event', onclick)
plt.show()
# Case B: Running PyQt5 + Matplotlib widget
# Note that nothing is printed when you click in the figure
app = QtWidgets.QApplication(sys.argv)
window = TestWindow()
window.show()
sys.exit(app.exec_())