Search code examples
pythonpyqtpyqtgraph

How to map mouse position on a scatterplot?


I'm trying to create a polygon area to select points on a scatterplot. The problem is that I don't know how to map mouse position on the scatterplot.

I used mapFromDevice but the result is unsatisfactory because the position is rounded and not precise (I guess this is because it adapts to the scatterplot coordinate system).

enter image description here

I don't know if I'm using a bad approach.

from PyQt5 import QtGui
from PyQt5.QtWidgets import *
from PyQt5 import QtCore
import pyqtgraph as pg
import sys

class MVAQPlot(QWidget):

    def __init__(self):
        QWidget.__init__(self)
        self.resize(600,600)
        pg.setConfigOption('background', 'w')
        pg.setConfigOption('foreground', 'k')

        self._graphics_layout = pg.GraphicsLayoutWidget(self)
        layout = QHBoxLayout()
        layout.addWidget(self._graphics_layout)
        self.setLayout(layout)

        self._scores = ([1, 1.5, 3, 5, 5], [1, 1.5, 3.4, 4, 2])

        self._roi = pg.PolyLineROI([], pen=(5))
        self._scatter_plot = pg.ScatterPlotItem()
        self._scatter_plot.setData(self._scores[0], self._scores[1])
        plot = self._graphics_layout.addPlot()
        plot.addItem(self._scatter_plot)
        plot.addItem(self._roi)

        self._selection_timer = QtCore.QTimer()
        self._selection_timer.timeout.connect(self.update_selection)

        self._creating_region = False
        self._last_point = None

    def mousePressEvent(self, event):
        if event.button() == QtCore.Qt.LeftButton:
            if self._creating_region:
                self._creating_region = False
                self._selection_timer.stop()
                roi_shape = self._roi.mapToItem(self._scatter_plot, self._roi.shape())
                self._points = list()
                for i in range(len(self._scores[0])):
                    self._points.append(QtCore.QPoint(self._scores[0][i], self._scores[1][i]))
                selected = [roi_shape.contains(pt) for pt in self._points]
                print('Selected points: ' + str(selected))
                self._roi.clearPoints()
            else:
                self._creating_region = True
                self._selection_points = list()
                self._selection_timer.start(1000)

    def update_selection(self):
        if self._roi is not None:
            point = QtCore.QPoint(self._graphics_layout.lastMousePos[0],
                                  self._graphics_layout.lastMousePos[1])
            self._selection_points.append(point)
            region = QtGui.QPolygon(self._selection_points)
            mapped_point = self._scatter_plot.mapFromDevice(region)
            self._roi.setPoints(mapped_point)

if __name__ == '__main__':
    app = QApplication(sys.argv)
    widget = MVAQPlot()
    widget.show()
    sys.exit(app.exec_())

I don't know how to draw a precise PolyLineROI on the scatterplot. Something like this:

enter image description here


Solution

  • The problem is that you are using QPoint and QPolygon that only support whole numbers, and if you pass a float then round it. The solution is to use QPointF and QPolygonF:

    def update_selection(self):
        if self._roi is not None:
            point = QtCore.QPointF(
                self._graphics_layout.lastMousePos[0],
                self._graphics_layout.lastMousePos[1],
            )
            self._selection_points.append(point)
            region = QtGui.QPolygonF(self._selection_points)
            mapped_point = self._scatter_plot.mapFromDevice(region)
            self._roi.setPoints(mapped_point)