3

I have a library of draggable matplotlib objects that I'm trying to utilize with a PyQt5 GUI. The objects' event listeners function correctly in a standalone matplotlib figure window but do not function when the figure is embedded in a QT widget. Both plots render correctly and there are no error messages when I try to drag the patch in the QT widget.

Object MCVE:

import matplotlib.patches as patches

class _DragObj:
    def __init__(self, ax):
        self.parentcanvas = ax.figure.canvas
        self.parentax = ax

        self.clickpress = self.parentcanvas.mpl_connect('button_press_event', self.on_click)
        self.clicked = False

    def on_click(self, event):
        if event.inaxes != self.parentax: return

        self.mousemotion = self.parentcanvas.mpl_connect('motion_notify_event', self.on_motion)
        self.clickrelease = self.parentcanvas.mpl_connect('button_release_event', self.on_release)

        self.clickx = event.xdata  
        self.clicky = event.ydata

        self.clicked = True

    def on_release(self, event):
        self.clicked = False
        self.disconnect()

    def disconnect(self):
        self.parentcanvas.mpl_disconnect(self.mousemotion)
        self.parentcanvas.mpl_disconnect(self.clickrelease)
        self.parentcanvas.draw()

    def stopdrag(self):
        self.myobj.set_url('')
        self.parentcanvas.mpl_disconnect(self.clickpress)

class _DragPatch(_DragObj):
    def __init__(self, ax, xy):
        super().__init__(ax)

        self.oldxy = xy

    def on_motion(self, event):
        if not self.clicked: return
        if event.inaxes != self.parentax: return

        oldx, oldy = self.oldxy
        dx = event.xdata - self.clickx
        dy = event.ydata - self.clicky
        newxy = [oldx + dx, oldy + dy]
        self.myobj.xy = newxy

        self.parentcanvas.draw()

    def on_release(self, event):
        self.clicked = False
        self.oldxy = self.myobj.xy

        self.disconnect()

class DragRectangle(_DragPatch):
    def __init__(self, ax, xy, width, height, angle=0.0, **kwargs):
        self.myobj = patches.Rectangle(xy, width, height, angle, **kwargs)
        ax.add_artist(self.myobj)

        super().__init__(ax, xy)

The functioning matplotlib example:

import minidrag
import matplotlib.pyplot as plt

fig = plt.figure()
ax = fig.add_subplot(111)
rect = minidrag.DragRectangle(ax, (0, 0), 2, 1)

ax.set_xlim(-5, 5)
ax.set_ylim(-5, 5)

plt.show()

The nonfunctional PyQT example:

import sys
from PyQt5 import QtWidgets as QtW
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.backends.backend_qt5agg import NavigationToolbar2QT as NavigationToolbar
from matplotlib.figure import Figure
import minidrag

class windowGUI(QtW.QDialog):
    def __init__(self):
        super().__init__()

        # Set up figure
        width_px = 800
        height_px = 600

        rect = QtW.QDesktopWidget().availableGeometry()
        screenrez = (rect.width(), rect.height())
        left_px = (screenrez[0] - width_px)/2
        top_px = (screenrez[1] - height_px)/2
        self.setGeometry(left_px, top_px, width_px, height_px)
        self.canvas = PlotCanvas()

        layout = QtW.QVBoxLayout()
        layout.addWidget(NavigationToolbar(self.canvas, self))
        layout.addWidget(self.canvas)
        self.setLayout(layout)

class PlotCanvas(FigureCanvas):
    def __init__(self):
        fig = Figure(frameon=False)
        super().__init__(fig)
        super().setSizePolicy(QtW.QSizePolicy.Expanding, QtW.QSizePolicy.Expanding)
        super().updateGeometry()

        ax = fig.add_subplot(111)
        rect = minidrag.DragRectangle(ax, (0, 0), 2, 1)

        ax.set_xlim(-5, 5)
        ax.set_ylim(-5, 5)

app = QtW.QApplication(sys.argv)
window = windowGUI()
window.show()
sys.exit(app.exec_())

I'm using Python 3.6.0, matplotlib (2.0.0), and PyQt5 (5.8)

What am I missing?

4
  • Have you tried implementing def dragEnterEvent(self, event): event.acceptProposedAction() for all the embedding QWidgets? Otherwise the event can't propagate to the embedded widget. Btw you might need to do the same for dropEvent. See QWidget.dragEnterEvent. Commented Mar 7, 2017 at 14:02
  • Because AFAIK drag events are ignored by default. Maybe it is sufficient to set setAcceptDrops(True) (at least the corresponding property is False by default; see the C++ docs). You might also want to read the C++ drag & drop docs for more information on how drag & drop works within Qt (the pyqt references sometimes are a bit sparse when it comes to more specific topics). Commented Mar 7, 2017 at 14:55
  • It's not a drag & drop. Neither proposed solution works. Commented Mar 7, 2017 at 14:59
  • Yes you are right, the dragging only occurs within the matplotlib figure so qt shouldn't interfere here. Commented Mar 7, 2017 at 15:17

1 Answer 1

4

Turns out that the problem are not drag events but instead your minidrag.DragRectangle instance just being garbage collected (while the canvas remains showing the rectangle at its original position).

As a fix you can set the rectangle as an instance variable:

self.rect = minidrag.DragRectangle(ax, (0, 0), 2, 1)

(Tested with Python2.7 and PyQt4.)

Sign up to request clarification or add additional context in comments.

1 Comment

oh, that's annoying :)

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.