Skip to content
Advertisement

Transformation of coordinates between PyQt and matplotlib

I would like to show a context menu on the position of mouse click and then create a new line on that position in the graph.

For that I need both the PyQt position and the graph data position. I thought that I could use the matplotlib transformation functions, but somehow when clicking the lower left and upper right corners of the graph I get in the print values [-0.34, 30.73], [3.02, -1.49] instead of ~[-0.3, -0.9], ~[4.3, 42].

Can anyone fix the mistake I make in the code?

P.S. I know I can connect a matplotlib signal and get the correct data positions. But I would then need to transform those positions to PyQt positions in order to place the widget correctly, resulting in the same issue.

Follows a simplified code:

import sys
import matplotlib
matplotlib.use('Qt5Agg')

from PyQt5 import QtCore, QtWidgets

from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg
from matplotlib.figure import Figure

class MplCanvas(FigureCanvasQTAgg):
    def __init__(self, parent=None, width=5, height=4, dpi=100):
        fig = Figure(figsize=(width, height), dpi=dpi)
        self.axes = fig.add_subplot(111)
        super(MplCanvas, self).__init__(fig)

        self._menuPoint = None
        self.canvasMenu = QtWidgets.QMenu(self)
        ca = QtWidgets.QAction('Add line', self)
        ca.triggered.connect(self.onAddLineClicked)
        self.canvasMenu.addAction(ca)

    def mouseReleaseEvent(self, event):
        super().mouseReleaseEvent(event)
        self._menuPoint = event.pos()
        print(self.axes.transData.inverted().transform((self._menuPoint.x(), self._menuPoint.y())))
        if event.button() == QtCore.Qt.RightButton:
            self.canvasMenu.exec_(self.mapToGlobal(self._menuPoint))

    def onAddLineClicked(self):
        pass


class MainWindow(QtWidgets.QMainWindow):
    def __init__(self, *args, **kwargs):
        super(MainWindow, self).__init__(*args, **kwargs)
        sc = MplCanvas(self)
        sc.axes.plot([0, 1, 2, 3, 4], [10, 1, 20, 3, 40])
        self.setCentralWidget(sc)
        self.show()


app = QtWidgets.QApplication(sys.argv)
w = MainWindow()
app.exec_()

Thanks.

Advertisement

Answer

The coordinates returned by QMouseEvent.pos() are comprised between [0 – widget width] and [0 - widget height], while the figure coordinates are between [0 – 1]. You therefore need to divide the mouse pos() by the widget width and height. There is also the subtlety that the Qt coordinates are from the upper left corner, while the matplotlib coordinates are from the lower left corner.

Once you have your position in figure coordinates, it is relatively straightforward to convert them in data coordinates. You could also convert them to Axes coordinates to test whether the click was inside the axes or not.

def mouseReleaseEvent(self, event):
    super().mouseReleaseEvent(event)
    self._menuPoint = event.pos()
    w, h = self.get_width_height()
    xfig = event.x()/w
    yfig = 1-(event.y()/h)  # necessary because Qt coordinates are from upper left, while matplotlib's are from
                            # lower left
    x, y = self.axes.transData.inverted().transform(self.fig.transFigure.transform([xfig, yfig]))
    print(event.pos(), x, y)
    if event.button() == QtCore.Qt.RightButton:
        self.canvasMenu.exec_(self.mapToGlobal(self._menuPoint))
User contributions licensed under: CC BY-SA
5 People found this is helpful
Advertisement