(详细分析)如何使用pyqt5和pyqtgraph在图像上增加图例并显示鼠标位置处的变量值

目录

  • 一、效果展示
  • 二、资料参考
  • 三、实例详解
  • 四、自定义案例

一、效果展示

开门见山,上效果:
在这里插入图片描述
窗口共有三个部分

  • 图2为所有数据画出的折线图
  • 图1为图2的蓝色矩形区域处的放大显示
  • 右上角的图例可以根据鼠标在图1中的移动,实时显示鼠标所在x轴处的折线y值

二、资料参考

  • pyqtgraph的examples的Crosshair / Mouse interaction
    在这里插入图片描述
    安装好后pyqtgraph包之后,上面的窗口在命令行中输入python -m pyqtgraph.examples即可。

三、实例详解

详细解释见代码注释:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
# # **********************************************************************
# CrossHair,借鉴显示鼠标所在x值处的线的y值
"""
Demonstrates some customized mouse interaction by drawing a crosshair that follows
the mouse.
"""

# import initExample ## Add path to library (just for examples; you do not need this)
import numpy as np
import pyqtgraph as pg
from pyqtgraph.Qt import QtGui, QtCore
from pyqtgraph.Point import Point

#generate layout
app = QtGui.QApplication([])  # 初始化app
win = pg.GraphicsLayoutWidget(show=True)  # 设置widget,并属性show为True,即显示
win.setWindowTitle('pyqtgraph example: crosshair')  # 设置窗口名称
label = pg.LabelItem(justify='right')  # 设置label用于跟随鼠标显示横纵坐标有效值
win.addItem(label)  # 定义了label之后,要addItem之后才会真正加进去
p1 = win.addPlot(row=1, col=0)  # p1为p2的区域放大,并显示鼠标所在位置值
p2 = win.addPlot(row=2, col=0)  # p2为全部数据画出的折线致密的原图

# 定义区域
region = pg.LinearRegionItem()
region.setZValue(10)  # 不过这一句话好像没什么作用
# Add the LinearRegionItem to the ViewBox, but tell the ViewBox to exclude this
# item when doing auto-range calculations.
p2.addItem(region, ignoreBounds=True)

#pg.dbg()
p1.setAutoVisible(y=True)


#create numpy arrays
#make the numbers large to show that the xrange shows data from 10000 to all the way 0
data1 = 10000 + 15000 * pg.gaussianFilter(np.random.random(size=10000), 10) + 3000 * np.random.random(size=10000)
data2 = 15000 + 15000 * pg.gaussianFilter(np.random.random(size=10000), 10) + 3000 * np.random.random(size=10000)

# 画图
p1.plot(data1, pen="r")
p1.plot(data2, pen="g")

p2.plot(data1, pen="w")

# 设置图2中放大区域被移动后的触发函数
def update():
    region.setZValue(10)
    minX, maxX = region.getRegion()  # 调整p1的横轴显示区域坐标范围
    p1.setXRange(minX, maxX, padding=0)

region.sigRegionChanged.connect(update)

def updateRegion(window, viewRange):
    rgn = viewRange[0]
    region.setRegion(rgn)

p1.sigRangeChanged.connect(updateRegion)

# 初始的region位置
region.setRegion([1000, 2000])

# 设置跟随鼠标移动的线
#cross hair
vLine = pg.InfiniteLine(angle=90, movable=False)  # angle控制线相对x轴正向的相对夹角
hLine = pg.InfiniteLine(angle=0, movable=False)
p1.addItem(vLine, ignoreBounds=True)
p1.addItem(hLine, ignoreBounds=True)


vb = p1.vb

# 跟随鼠标移动,提取鼠标的横轴值,并自定义纵轴的值显示
def mouseMoved(evt):
    pos = evt[0]  ## using signal proxy turns original arguments into a tuple
    if p1.sceneBoundingRect().contains(pos):
        mousePoint = vb.mapSceneToView(pos)
        # 建议不用int,精度高时用float,这样可以显示横坐标的小数
        index = int(mousePoint.x())
        if index > 0 and index < len(data1):
            label.setText("<span style='font-size: 12pt'>x=%0.1f,   <span style='color: red'>y1=%0.1f</span>,   <span style='color: green'>y2=%0.1f</span>" % (mousePoint.x(), data1[index], data2[index]))
        vLine.setPos(mousePoint.x())
        hLine.setPos(mousePoint.y())


# 设置鼠标移动的触发,限制速率,移动则触发mouseMoved函数
proxy = pg.SignalProxy(p1.scene().sigMouseMoved, rateLimit=60, slot=mouseMoved)
#p1.scene().sigMouseMoved.connect(mouseMoved)


## Start Qt event loop unless running in interactive mode or using pyside.
if __name__ == '__main__':
    import sys
    if (sys.flags.interactive != 1) or not hasattr(QtCore, 'PYQT_VERSION'):
        QtGui.QApplication.instance().exec_()

四、自定义案例

p1为加了该功能的,p2未加,做对比,可以不管它。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
class ChildWinSystemStatus(QDialog, Ui_SystemStatus):
    signal_plot = pyqtSignal(object, object, object)
    # 当主窗口被关闭后,也关闭子窗口
    signal_exit = pyqtSignal(object)

    def __init__(self, qSystem):
        super(ChildWinSystemStatus, self).__init__()
        self.setupUi(self)
        # 设置窗口最小化与最大化按钮,关闭按钮置灰
        self.setWindowFlags(Qt.WindowMaximizeButtonHint | Qt.WindowMinimizeButtonHint)

        self.label1 = pg.LabelItem(justify='right')
        self.guiplot_consensus.addItem(self.label1)
        # label2 = pg.LabelItem(justify='right')
        # self.guiplot_growthRate.addItem(label2)

        # 美化
        self.p1 = self.guiplot_consensus.addPlot(title="Consensus Time", row=1, col=0)
        self.p1.setAutoVisible(y=True)
        self.p1.showGrid(x=True, y=True)  # 设置网格
        self.p1.setLabel('bottom', "Simulation time")
        self.vLine1 = pg.InfiniteLine(angle=90, movable=False)
        self.hLine1 = pg.InfiniteLine(angle=0, movable=False)
        self.p1.addItem(self.vLine1, ignoreBounds=True)
        self.p1.addItem(self.hLine1, ignoreBounds=True)
        self.vb1 = self.p1.vb
        # 将鼠标移动事件,设置触发函数self.mouseMoved
        self.proxy = pg.SignalProxy(self.p1.scene().sigMouseMoved, rateLimit=60, slot=self.mouseMoved)

        self.p2 = self.guiplot_growthRate.addPlot(title="Growth Rate")
        # self.p2.setLabel('bottom', "Simulation time")
        self.p2.showGrid(x=True, y=True)

        self.signal_plot.connect(self.child_plotSystem)
        self.signal_exit.connect(self.closeEvent)

        self.queue_system = qSystem

        self.consensusLatency = []
        self.rateGrowth = []
        self.simulationTime = []

    def mouseMoved(self, evt):
        pos = evt[0]  ## using signal proxy turns original arguments into a tuple
        if self.p1.sceneBoundingRect().contains(pos):
            mousePoint = self.vb1.mapSceneToView(pos)
            # 一定要注意用float,这个bug花了一个小时才找出来。注意图中y值变化时刻的x,发现都是x超过整数时才变化,找出的本bug
            index = float(mousePoint.x())
            # 当未启动时,鼠标移动,只显示x的值
            if len(self.simulationTime) == 0:
                self.label1.setText("<span style='font-size: 12pt'>SimTime=%0.2f s,   <span style='color: white'>ConTime=   s</span>" % (mousePoint.x()))
                # self.label1.setText("<span style='font-size: 12pt;color: white'>SimTime=%0.1f s, ConTime=    </span>" % (mousePoint.x()))
            # 调整,使label精确显示y值
            if len(self.simulationTime) >= 2 and index > self.simulationTime[0] and index < self.simulationTime[-1]:
                for i in range(len(self.simulationTime)):
                    if self.simulationTime[i] <= index < self.simulationTime[i + 1]:
                        self.label1.setText("<span style='font-size: 12pt'>SimTime=%0.2f s,   <span style='color: white'>ConTime=%0.2f s</span>" % (mousePoint.x(), self.consensusLatency[i]))
                        break
            self.vLine1.setPos(mousePoint.x())
            self.hLine1.setPos(mousePoint.y())

    def closeEvent(self, event):
        os._exit(0)

    def analyzeSystem(self, dataSystem):
        value = dataSystem['value']
        if dataSystem['stat'] == 0:
            self.consensusLatency.append(value)
            self.simulationTime.append(dataSystem['time_simulated'])
            rate_growth = 1 / value
            self.rateGrowth.append(round(rate_growth, 4))
            # 向连接槽发射信号,触发窗口类中画图函数
            self.signal_plot.emit(self.consensusLatency, self.rateGrowth, self.simulationTime)
        if dataSystem['stat'] == 1:
            value_rate = round(value * 100, 2)
            self.rate_anti.setText(str(value_rate) + '%')

    def child_plotSystem(self, consensusLatency, growth_rate, list_time):
        if consensusLatency:
            self.p1.plot(list_time, consensusLatency, pen=(0, 255, 0))
            self.p2.plot(list_time, growth_rate, pen=(0, 255, 0))
        else:
            pass

    def run(self):
        while True:
            if not self.queue_system.empty():
                dataSystem = self.queue_system.get()
                if dataSystem == 'exit':
                    self.signal_exit.emit('exit')
                    break
                self.analyzeSystem(dataSystem)

效果如下:
在这里插入图片描述