1)问题动机和使用场景
最近做一个项目,需要的功能是使用鼠标添加节点(鼠标点击的位置即节点坐标),同时点坐标添加之后还能可视化的动态显示出来。
2)问题解决过程
在平常python作图中,我常用的工具是matplotlib。但它好像不能实现我需要的功能,感觉matplotlib完全是依靠数据驱动(即提供数据即能画图),可是没法实现使用者与图形的双向交互(即使用者通过鼠标点击就可以生成新的点)。一番搜索之后,发现pyqt貌似可以满足我的需求,并且还能找到一些相关的代码。说干就干,我借鉴网上的代码,再结合自己的需求,最终实现了我需要的功能。
3)需求框架和思路
我的需求主要是分为以下几个部分:
1)给定节点坐标,即可以以散点图的形式显示出来,这个pyqtgraph库实现,关键步骤代码如下。
# 创建 PlotWidget 对象
self.pw = pg.PlotWidget()
# 获取绘制散点图对象
self.scatter = self.pw.plot(pen=None, symbol='o')
# 给点x,y坐标,绘制散点图
self.scatter.setData(self.x, self.y)
2)节点数目或者坐标变动后,位置可以实时更新。
# 启动定时器,每隔100ms通知刷新一次数据
self.timer = QtCore.QTimer()
self.timer.timeout.connect(self.updateData)
self.timer.start(100)
3)鼠标左击作图界面,添加新的点。这个部分是最困扰我的,之前都没有用过pyqt,所以也不不知道怎么样捕捉鼠标单击信号,更不清楚提取对应的位置坐标。在实际做的时候,我也把这部分分为了两步:a)捕捉鼠标单击信号,主要代码如下:
# 捕捉鼠标单击事件
self.scatter.scene().sigMouseClicked.connect(self.mouse_clicked)
# 捕捉到单击事件后,需要做的动作,在mouse_clicked函数里完成
def mouse_clicked(self,event):
self.x.append(self.new_point_x)
self.y.append(self.new_point_y)
b)获取鼠标在单击时对应在图形位置的坐标,在这里我遇到了一个坑。 照理sigMouseClicked返回值是一个event,里面就包含了单击时刻的坐标,但是这个坐标不准确,跟实际的位置有些出入。经过测试,反而是通过鼠标移动事件提取的坐标是准确无误的。
# 捕捉鼠标移动事件
self.scatter.scene().sigMouseMoved.connect(self.mouseover)
# 捕捉到单击事件后,需要做的动作,在mouseover函数里完成,主要是提取点的坐标
def mouseover(self,pos):
# 参数pos 是像素坐标,需要 转化为 刻度坐标
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)总结
以上就是解决我需求的全部思路。全部完整的代码如下,复制后直接可以用:
# 捕捉鼠标移动事件
self.scatter.scene().sigMouseMoved.connect(self.mouseover)
# 捕捉到单击事件后,需要做的动作,在mouseover函数里完成,主要是提取点的坐标
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作图')
# 创建 PlotWidget 对象
self.pw = pg.PlotWidget()
# 设置图表标题
self.pw.setTitle("节点分布图",
color='008080',
size='12pt')
# 设置上下左右的label
self.pw.setLabel("left","纵坐标")
self.pw.setLabel("bottom","横坐标")
self.pw.setXRange(min=0, # 最小值
max=FieldRadius) # 最大值
# 设置Y轴 刻度 范围
self.pw.setYRange(min=0, # 最小值
max=FieldRadius) # 最大值
# 显示表格线
self.pw.showGrid(x=True, y=True)
# 背景色改为白色
self.pw.setBackground('w')
# 居中显示 PlotWidget
self.setCentralWidget(self.pw)
# 实时显示应该获取 PlotDataItem对象, 调用其setData方法,
# 这样只重新plot该曲线,性能更高
self.scatter = self.pw.plot(pen=None, symbol='o')
self.i = 0
self.x = [] # x轴的值
self.new_point_x = 0
self.y = [] # y轴的值
self.new_point_y = 0
self.setMouseTracking(False)
self.scatter.scene().sigMouseMoved.connect(self.mouseover)
self.scatter.scene().sigMouseClicked.connect(self.mouse_clicked)
# 启动定时器,每隔1秒通知刷新一次数据
self.timer = QtCore.QTimer()
self.timer.timeout.connect(self.updateData)
self.timer.start(100)
def updateData(self):
self.scatter.setData(self.x, self.y)
# 鼠标移动事件,用于获取精确坐标(好像鼠标单击的坐标不准确)
def mouseover(self,pos):
# 参数pos 是像素坐标,需要 转化为 刻度坐标
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()
# 鼠标单击事件,用于鼠标单击后事件的处理,包括:
# 1)添加新坐标
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_()
我的实验结果: