pyqt learning - use the mouse to click to get the position of the point and draw

Keywords: Python Back-end

1) Problem motivation and usage scenarios

In a recent project, the required function is to use the mouse to add nodes (the position where the mouse clicks is the node coordinates), and the point coordinates can be displayed visually and dynamically after being added.

2) Problem solving process

In ordinary python drawing, my commonly used tool is matplotlib. However, it seems that it can not achieve the functions I need. It feels that matplotlib completely depends on data-driven (that is, it can draw pictures by providing data), but it can not realize the two-way interaction between users and graphics (that is, users can generate new points by clicking with the mouse). After a search, I found that pyqt seemed to meet my needs, and I could find some relevant code. I learned from the online code, combined with my own needs, and finally realized the functions I needed.

3) Requirements framework and ideas

My requirements are mainly divided into the following parts:

1) Given the node coordinates, it can be displayed in the form of scatter diagram. This pyqtgraph library is implemented. The key step codes are as follows.
# Create a PlotWidget object
self.pw = pg.PlotWidget()
# Gets the scatter plot object
self.scatter = self.pw.plot(pen=None, symbol='o')
# Give the X and Y coordinates of points and draw a scatter diagram
self.scatter.setData(self.x, self.y)
2) After the number of nodes or coordinates change, the position can be updated in real time.
# Start the timer and notify to refresh the data every 100ms
self.timer = QtCore.QTimer()
self.timer.timeout.connect(self.updateData)
self.timer.start(100)
3) Left click the drawing interface to add a new point. This part bothers me most. I haven't used pyqt before, so I don't know how to capture the mouse click signal, let alone extract the corresponding position coordinates. In practice, I also divide this part into two steps: a) capture the mouse click signal. The main code is as follows:
# Capture mouse click events
self.scatter.scene().sigMouseClicked.connect(self.mouse_clicked)
# After capturing the click event, you need to do the action in mouse_ Completed in the clicked function
def mouse_clicked(self,event):
    self.x.append(self.new_point_x)
    self.y.append(self.new_point_y)
b) Get the coordinates corresponding to the graphic position when the mouse clicks. Here I encountered a pit. Normally, the return value of sigMouseClicked is an event, which contains the coordinates of the click time, but this coordinate is inaccurate, which is somewhat different from the actual position. After testing, the coordinates extracted by moving the mouse event are accurate. So in the whole program, I used two trigger events, one is mouse click and the other is mouse movement. The main codes of mouse movement are as follows:
# Capture mouse movement events
self.scatter.scene().sigMouseMoved.connect(self.mouseover)
# After capturing the click event, the actions to be done are completed in the mouseover function, mainly extracting the coordinates of the points
def mouseover(self,pos):
    # The parameter pos is a pixel coordinate, which needs to be converted into a scale coordinate
    act_pos = self.scatter.mapFromScene(pos)
    if type(act_pos) != QtCore.QPointF:
        return
    # print("over_1:",act_pos.x(), act_pos.y())
    self.new_point_x = act_pos.x()
    self.new_point_y = act_pos.y()

4) Summary

The above is all the ideas to solve my needs. All complete codes are as follows, which can be used directly after copying:

# Capture mouse movement events
self.scatter.scene().sigMouseMoved.connect(self.mouseover)
# After capturing the click event, the actions to be done are completed in the mouseover function, mainly extracting the coordinates of the points
from PySide2 import QtGui, QtWidgets, QtCore
# from pyqtgraph.Qt import  QtCore
import pyqtgraph as pg
import sys
from random import randint
import numpy as np

FieldRadius = 100

class MainWindow(QtWidgets.QMainWindow):
    def __init__(self):
        super().__init__()
        self.setWindowTitle('pyqtgraph Mapping')
        # Create a PlotWidget object
        self.pw = pg.PlotWidget()
        # Set chart title
        self.pw.setTitle("Node distribution diagram",
                         color='008080',
                         size='12pt')
        # Set the upper, lower, left and right label s
        self.pw.setLabel("left","Ordinate")
        self.pw.setLabel("bottom","Abscissa")

        self.pw.setXRange(min=0,  # minimum value
                          max=FieldRadius)  # Maximum
        # Set Y-axis scale range
        self.pw.setYRange(min=0,  # minimum value
                          max=FieldRadius)  # Maximum
        # Show table lines
        self.pw.showGrid(x=True, y=True)
        # Change the background color to white
        self.pw.setBackground('w')
        # Center the PlotWidget
        self.setCentralWidget(self.pw)

        # The real-time display should obtain the PlotDataItem object and call its setData method,
        # In this way, only the curve is re plot ted, and the performance is higher
        self.scatter = self.pw.plot(pen=None, symbol='o')

        self.i = 0
        self.x = [] # Value of x-axis
        self.new_point_x = 0
        self.y = [] # Value of y-axis
        self.new_point_y = 0

        self.setMouseTracking(False)
        self.scatter.scene().sigMouseMoved.connect(self.mouseover)
        self.scatter.scene().sigMouseClicked.connect(self.mouse_clicked)

        # Start the timer and notify to refresh the data every 1 second
        self.timer = QtCore.QTimer()
        self.timer.timeout.connect(self.updateData)
        self.timer.start(100)

    def updateData(self):
        self.scatter.setData(self.x, self.y)

    # Mouse movement event, used to obtain accurate coordinates (as if the coordinates clicked by the mouse were inaccurate)
    def mouseover(self,pos):
        # The parameter pos is a pixel coordinate, which needs to be converted into a scale coordinate
        act_pos = self.scatter.mapFromScene(pos)
        if type(act_pos) != QtCore.QPointF:
            return
        # print("over_1:",act_pos.x(), act_pos.y())
        self.new_point_x = act_pos.x()
        self.new_point_y = act_pos.y()

    # Mouse click event is used to process events after mouse click, including:
    # 1) Add new coordinates
    def mouse_clicked(self,event):

        self.x.append(self.new_point_x)
        self.y.append(self.new_point_y)
        # print("my position is:",self.xx,self.yy)

if __name__ == '__main__':
    app = QtWidgets.QApplication()
    main = MainWindow()
    main.show()
    app.exec_()

My experimental results:
[1] Main reference website White moon black feather

Posted by paulmo on Mon, 06 Dec 2021 11:56:29 -0800