Search code examples
pyqtpyqt5pysidepyqtgraph

Select points from a scattr plot using mouse cross hair - PyQt


I am new to PyQt and Im developing a utility where a user can import data from an excel file and plot its X and Y in a 2d scatter plot using below code:

def plot_2d_scatter(graphWidget,x,z,color=(66, 245, 72)):
    graphWidget.clear()
    brush = pg.mkBrush(color)
    scatter = pg.ScatterPlotItem(size=5, brush=brush)
    scatter.addPoints(x,z)
    graphWidget.addItem(scatter)

Now I want a functionality which will allow the user to move his mouse over the scatter plot points using a cross hair / pointer / etc and select points on the scatter plot.

Whenever the user does a left click on the crosshair / marker on the scatter plot, I want its x,y coordinates to be saved for further use.

I have already tried the below snippet from somewhere on internet for using mouse events and getting my scatter points , but this didnt give me a cross hair that falls on my scatter points

def mouseMoved(self, evt):
    pos = evt
    if self.plotWidget.sceneBoundingRect().contains(pos):
        mousePoint = self.plotWidget.plotItem.vb.mapSceneToView(pos)
        mx = np.array([abs(float(i) - float(mousePoint.x())) for i in self.plotx])
        index = mx.argmin()
        if index >= 0 and index < len(self.plotx):
            self.cursorlabel.setHtml(
                "<span style='font-size: 12pt'>x={:0.1f}, \
                 <span style='color: red'>y={:0.1f}</span>".format(
                    self.plotx[index], self.ploty[index])
            )
        self.vLine.setPos(self.plotx[index])
        self.hLine.setPos(self.ploty[index])

Any guidance is thankfully appreciated


Solution

  • my best fast effort, never used pg untill today:

    import sys
    from PyQt5.QtWidgets import QApplication, QMainWindow, QDesktopWidget, QWidget
    
    from PyQt5.QtCore import Qt
    
    from PyQt5 import QtGui, QtCore, QtWidgets
    
    import pyqtgraph as pg
    
    import numpy as np
    
    class MyApp(QMainWindow):
        
        def __init__(self, parent=None):
            super(MyApp, self).__init__(parent)
            
            
            self.resize(781, 523)
            
            self.graphWidget = pg.PlotWidget()
            self.setCentralWidget(self.graphWidget)
            
            self.show()
            
            self.x = [1,2,3,4,5,6,7,8,9,5,6,7,8]
            
            self.y = [1,2,3,4,5,6,7,8,9,5,6,7,8]
            
            
            # self.y.reverse()
            
                
            self.plot_2d_scatter(self.graphWidget, self.x, self.y)
            
            self.cursor = Qt.CrossCursor
            
            # self.cursor = Qt.BlankCursor
            
            self.graphWidget.setCursor(self.cursor)
           
            # Add crosshair lines.
            self.crosshair_v = pg.InfiniteLine(angle=90, movable=False)
            self.crosshair_h = pg.InfiniteLine(angle=0, movable=False)
            self.graphWidget.addItem(self.crosshair_v, ignoreBounds=True)
            self.graphWidget.addItem(self.crosshair_h, ignoreBounds=True)
            self.cursorlabel = pg.TextItem()
    
            
            self.graphWidget.addItem(self.cursorlabel)
            
            
            self.proxy = pg.SignalProxy(self.graphWidget.scene().sigMouseMoved, rateLimit=60, slot=self.update_crosshair)
            
            self.mouse_x = None
            
            self.mouse_y = None
            
        def plot_2d_scatter(self,graphWidget,x,z,color=(66, 245, 72)):
            # graphWidget.clear()
            brush = pg.mkBrush(color)
            scatter = pg.ScatterPlotItem(size=5, brush=brush)
            scatter.addPoints(x,z)
            graphWidget.addItem(scatter)
        
        
    
        def update_crosshair(self, e):
                
            pos = e[0]
            if self.graphWidget.sceneBoundingRect().contains(pos):
                mousePoint = self.graphWidget.plotItem.vb.mapSceneToView(pos)
                mx = np.array([abs(float(i) - float(mousePoint.x())) for i in self.x])
                index = mx.argmin()
                if index >= 0 and index < len(self.x):
                    self.cursorlabel.setText(
                            str((self.x[index], self.y[index])))
                    
                    self.crosshair_v.setPos(self.x[index])
                    self.crosshair_h.setPos(self.y[index]) 
                    
                    self.mouse_x = self.crosshair_v.setPos(self.x[index])
                    self.mouse_y = self.crosshair_h.setPos(self.y[index])
                    
                    self.mouse_x = (self.x[index])
                    self.mouse_y = (self.y[index])
                
                
        def mousePressEvent(self, e):
            if e.buttons() & QtCore.Qt.LeftButton:
                print('pressed')
                
                # if self.mouse_x in self.x and self.mouse_y in self.y:
               
                print(self.mouse_x, self.mouse_y)
                
        
    if __name__ == '__main__':
    
        app = QApplication(sys.argv)
        myapp = MyApp()
        # myapp.show()
    
        try:
            sys.exit(app.exec_())
        except SystemExit:
            print('Closing Window...')
    
    

    it just prints out the coordinate of pressed point in graph

    copied from https://www.pythonguis.com/faq/pyqt-show-custom-cursor-pyqtgraph/ and your piece of code result looks like:

    enter image description here

    there are other examples on SO like Trying to get cursor with coordinate display within a pyqtgraph plotwidget in PyQt5 and others