如何在pyqtgraph中更新绘图?

2024-09-30 22:24:34 发布

您现在位置:Python中文网/ 问答频道 /正文

我正在尝试使用PyQt5和pyqtgraph创建一个用户界面。我做了两个复选框,每当我选择它们时,我想绘制代码中可用的两个数据集中的一个,每当我取消选择按钮时,我想让它清除相应的曲线。有两个文本为A1A2的复选框,每个复选框绘制一组数据

我有两个问题:

1-如果我选择A1,它将绘制与A1关联的数据,只要我不选择A2,通过取消选择A1,我就可以清除与A1关联的数据。 但是,如果我选中A1框,然后再选中A2框,则取消选中A1不会清除关联的绘图。在这种情况下,如果我选择绘制随机数据,而不是像sin这样的确定性曲线,我会看到通过选择其中一个按钮,添加了新数据,但无法删除

2-实际应用程序有96个按钮,每个按钮应与一个数据集相关联。我认为我编写代码的方式效率很低,因为我需要将相同的代码复制一个按钮和数据集96次。有没有办法将我在下面介绍的玩具代码推广到任意数量的复选框?或者,对每个按钮使用/复制几乎相同的代码是实现这一点的通常和正确的方法

代码是:

from PyQt5 import QtWidgets, uic, QtGui
import matplotlib.pyplot as plt
from matplotlib.widgets import SpanSelector
import numpy as np
import sys
import string
import pyqtgraph as pg
from pyqtgraph.Qt import QtGui, QtCore

app = QtWidgets.QApplication(sys.argv)

x = np.linspace(0, 3.14, 100)
y1 = np.sin(x)#Data number 1 associated to checkbox A1
y2 = np.cos(x)#Data number 2 associated to checkbox A2

#This function is called whenever the state of checkboxes changes
def todo():
    if cbx1.isChecked():
        global curve1
        curve1 = plot.plot(x, y1, pen = 'r')
    else:
        try:
            plot.removeItem(curve1)
        except NameError:
            pass
    if cbx2.isChecked():
        global curve2
        curve2 = plot.plot(x, y2, pen = 'y')
    else:
        try:
            plot.removeItem(curve2)
        except NameError:
            pass  
#A widget to hold all of my future widgets
widget_holder = QtGui.QWidget()

#Checkboxes named A1 and A2
cbx1 = QtWidgets.QCheckBox()
cbx1.setText('A1')
cbx1.stateChanged.connect(todo)

cbx2 = QtWidgets.QCheckBox()
cbx2.setText('A2')
cbx2.stateChanged.connect(todo)

#Making a pyqtgraph plot widget
plot = pg.PlotWidget()

#Setting the layout
layout = QtGui.QGridLayout()
widget_holder.setLayout(layout)

#Adding the widgets to the layout
layout.addWidget(cbx1, 0,0)
layout.addWidget(cbx2, 0, 1)
layout.addWidget(plot, 1,0, 3,1)

widget_holder.adjustSize()
widget_holder.show()

sys.exit(app.exec_())


    

Tags: 数据代码importa2plota1绘制widget
3条回答

在下文中,我采用了一种更为暴力的方法,同时假设绘制所有曲线所需的时间可以忽略不计:

import numpy as np
import sys
import pyqtgraph as pg
from pyqtgraph.Qt import QtGui, QtWidgets

app = QtWidgets.QApplication(sys.argv)

x = np.linspace(0, 3.14, 100)
y1 = np.sin(x)#Data number 1 associated to checkbox A1
y2 = np.cos(x)#Data number 2 associated to checkbox A2

curves = [y1, y2]
pens = ["r", "y"]

#This function is called whenever the state of checkboxes changes
def plot_curves(state):
    plot.clear()
    for checkbox, curve, pen in zip(checkboxes, curves, pens):
        if checkbox.isChecked():
            plot.plot(x, curve, pen=pen)

#A widget to hold all of my future widgets
widget_holder = QtGui.QWidget()

#Making a pyqtgraph plot widget
plot = pg.PlotWidget()

#Setting the layout
layout = QtGui.QGridLayout()
widget_holder.setLayout(layout)

checkboxes = [QtWidgets.QCheckBox() for i in range(2)]
for i, checkbox in enumerate(checkboxes):
    checkbox.setText(f"A{i+1}")
    checkbox.stateChanged.connect(plot_curves)
    layout.addWidget(checkbox, 0, i)

#Adding the widgets to the layout
layout.addWidget(plot, 1, 0, len(checkboxes), 0)

widget_holder.adjustSize()
widget_holder.show()

sys.exit(app.exec_())

现在您有了一个复选框列表,索引为0的复选框对应于索引为0的curves-列表中的数据。我每次都会绘制所有曲线,这会产生更可读的代码。但是,如果这确实会影响性能,则需要稍微复杂一点

我还尝试添加另一条曲线,结果似乎非常好:

Img with three curves

我在你的代码中发现了问题。让我们看看您的代码的作用:

  1. 将第一个绘图添加到小部件(无论是A1还是A2)时,您将获得PlotDataItem并将其存储在curve1curve2中。假设首先选中A1,然后todo函数首先检查复选框1是否选中,因此绘制数据并将其存储在curve1中,然后相同的函数检查复选框2。复选框2未选中,因此函数执行else语句,该语句将从绘图小部件中删除curve2,此变量不存在,因此可能会引发错误,但是,使用try语句时,错误永远不会出现

  2. 现在,选中A2框,函数首先检查复选框1,它被选中,因此函数将再次添加相同的绘图,但作为另一个PlotDataItem,并将其存储在curve1。到目前为止,您有两个相同数据的PlotDataItem(这意味着两个绘图),但只有最后一个存储在curve1中。该函数的下一步操作是检查复选框2,选中复选框后,它将绘制第二个数据并将其PlotDataItem保存在curve2

  3. 因此,当您现在取消选中复选框1时,您的函数首先检查复选框1(抱歉,如果它是重复的),它被取消选中,因此函数将删除存储在curve1中的PlotDataItem并执行此操作,但请记住,您有两个相同数据的绘图,因此对于我们(观众)来说,绘图不会消失。这就是问题所在,但它并没有到此结束,函数现在检查复选框2,它被选中,因此函数将添加第二个数据的另一个PlotDataItem,并将其存储在curve2。我们将再次遇到与第一个数据相同的问题

通过这个分析,我还学到了一些东西,PlotDataItem不会在“覆盖”存储它的变量时消失,从PlotWidget中删除时也不会消失。考虑到这一点,我对上一个答案的代码做了一些更改,因为每次我们选中之前选中的复选框和未选中的复选框时,旧代码将创建另一项。现在,如果创建了该项,我的函数将再次添加它,而不是创建另一个

我有一些建议:

  • 尝试使用对象,生成自己的小部件类。您可以避免调用全局变量,将它们作为类的属性传递。(就像我先前的回答)

  • 如果您想保持代码的原样(不使用类),为了使其正常工作,您可以添加另外两个带有复选框“状态”的变量,因此当您首先调用函数时,它会检查状态是否没有更改,并忽略该复选框。另外,请检查PlotDataItem是否在之前生成,并且只需再次添加它以避免生成更多项

  • 您的目标是对一组框或按钮执行此操作,尝试对所有框或按钮仅使用一个变量:例如,一个包含所有框/按钮(对象)的列表。然后,您可以通过索引管理它们中的任何一个。此外,还可以在该变量上执行循环,以将内部对象连接到同一函数

    my_buttons = [ QtGui.QPushButton() for _ in range(number_of_buttons) ]
    my_boxes= [ QtGui.QCheckBox() for _ in range(number_of_boxes) ]
    my_boxes[0].setText('Box 1 Here')
    my_boxes[2].setChecked(True)
    for i in range(number_of_boxes):
        my_boxes[i].stateChanged.connect(some_function)
    
  • 执行对象列表还有助于您轻松自动命名:

    my_boxes= [ QtGui.QCheckBox(f"Box number {i+1}") for i in range(number_of_boxes) ]
    my_boxes= [ QtGui.QCheckBox(str(i+1)) for i in range(number_of_boxes) ]
    my_boxes= [ QtGui.QCheckBox('Box {:d}'.format(i+1)) for i in range(number_of_boxes) ]
    

最后,这里是您的代码,其中有一些小的更改以使其正常工作:

from PyQt5 import QtWidgets, uic, QtGui
import matplotlib.pyplot as plt
from matplotlib.widgets import SpanSelector
import numpy as np
import sys
import string
import pyqtgraph as pg
from pyqtgraph.Qt import QtGui, QtCore

app = QtWidgets.QApplication(sys.argv)

x = np.linspace(0, 3.14, 100)
y1 = np.sin(x)#Data number 1 associated to checkbox A1
y2 = np.cos(x)#Data number 2 associated to checkbox A2

#This function is called whenever the state of checkboxes changes
def todo():
    global b1st, b2st, curve1, curve2
    if cbx1.isChecked() != b1st:
        b1st = cbx1.isChecked()
        if cbx1.isChecked():
            if curve1 is None:
                curve1 = plot.plot(x, y1, pen = 'r')
            else:
                plot.addItem(curve1)
        else:
            plot.removeItem(curve1)

    if cbx2.isChecked() != b2st:
        b2st = cbx2.isChecked()
        if cbx2.isChecked():
            if curve2 is None:
                curve2 = plot.plot(x, y2, pen = 'y')
            else:
                plot.addItem(curve2)
        else:
            plot.removeItem(curve2)

#A widget to hold all of my future widgets
widget_holder = QtGui.QWidget()

#Checkboxes named A1 and A2
cbx1 = QtWidgets.QCheckBox()
cbx1.setText('A1')
cbx1.stateChanged.connect(todo)
b1st = False
curve1 = None

cbx2 = QtWidgets.QCheckBox()
cbx2.setText('A2')
cbx2.stateChanged.connect(todo)
b2st = False
curve2 = None

#Making a pyqtgraph plot widget
plot = pg.PlotWidget()

#Setting the layout
layout = QtGui.QGridLayout()
widget_holder.setLayout(layout)

#Adding the widgets to the layout
layout.addWidget(cbx1, 0,0)
layout.addWidget(cbx2, 0, 1)
layout.addWidget(plot, 1,0, 3,1)

widget_holder.adjustSize()
widget_holder.show()

sys.exit(app.exec_())

下面是我做的一个很好的例子。 可以重用它来进行更多的绘图,而无需增加代码,只需更改self.num的值并使用函数add_data(x,y,ind)添加相应的数据,其中xy是数据的值ind是框的索引(从0n-1

import sys
import numpy as np
import pyqtgraph as pg
from pyqtgraph.Qt import QtCore, QtGui

class MyApp(QtGui.QWidget):
    def __init__(self):
        QtGui.QWidget.__init__(self)
        self.central_layout = QtGui.QVBoxLayout()
        self.plot_boxes_layout = QtGui.QHBoxLayout()
        self.boxes_layout = QtGui.QVBoxLayout()
        self.setLayout(self.central_layout)
        
        # Lets create some widgets inside
        self.label = QtGui.QLabel('Plots and Checkbox bellow:')
        
        # Here is the plot widget from pyqtgraph
        self.plot_widget = pg.PlotWidget()
        
        # Now the Check Boxes (lets make 3 of them)
        self.num = 6
        self.check_boxes = [QtGui.QCheckBox(f"Box {i+1}") for i in range(self.num)]
        
        # Here will be the data of the plot
        self.plot_data = [None for _ in range(self.num)]
        
        # Now we build the entire GUI
        self.central_layout.addWidget(self.label)
        self.central_layout.addLayout(self.plot_boxes_layout)
        self.plot_boxes_layout.addWidget(self.plot_widget)
        self.plot_boxes_layout.addLayout(self.boxes_layout)
        for i in range(self.num):
            self.boxes_layout.addWidget(self.check_boxes[i])
            # This will conect each box to the same action
            self.check_boxes[i].stateChanged.connect(self.box_changed)
            
        # For optimization let's create a list with the states of the boxes
        self.state = [False for _ in range(self.num)]
        
        # Make a list to save the data of each box
        self.box_data = [[[0], [0]] for _ in range(self.num)] 
        x = np.linspace(0, 3.14, 100)
        self.add_data(x, np.sin(x), 0)
        self.add_data(x, np.cos(x), 1)
        self.add_data(x, np.sin(x)+np.cos(x), 2)
        self.add_data(x, np.sin(x)**2, 3)
        self.add_data(x, np.cos(x)**2, 4)
        self.add_data(x, x*0.2, 5)
        

    def add_data(self, x, y, ind):
        self.box_data[ind] = [x, y]
        if self.plot_data[ind] is not None:
            self.plot_data[ind].setData(x, y)

    def box_changed(self):
        for i in range(self.num):
            if self.check_boxes[i].isChecked() != self.state[i]:
                self.state[i] = self.check_boxes[i].isChecked()
                if self.state[i]:
                    if self.plot_data[i] is not None:
                        self.plot_widget.addItem(self.plot_data[i])
                    else:
                        self.plot_data[i] = self.plot_widget.plot(*self.box_data[i])
                else:
                    self.plot_widget.removeItem(self.plot_data[i])
                break
        
if __name__ == "__main__":
    app = QtGui.QApplication(sys.argv)
    window = MyApp()
    window.show()
    sys.exit(app.exec_())

请注意,在dePlotWidget中,我使用plot()方法添加绘图,它返回一个PlotDataItem,保存在调用self.plot_data之前创建的列表中。 有了它,您可以很容易地从Plot Widget中删除它并再次添加它。此外,如果您的目标是更复杂的程序,例如,可以在运行时更改每个框的数据的程序,那么如果您在PlotDataItem上使用setData()方法,则绘图将更新,而不会出现重大问题

正如我在开始时所说的,这应该适用于许多复选框,因为选中/取消选中复选框时调用的函数首先将每个框的实际状态与前一个框(存储在self.state)进行比较,并且只对对应于该特定框的绘图进行更改。这样,您就避免了每次选中/取消选中一个复选框时,为每个复选框执行一个函数,并为所有de框执行replot(如user8408080)。我并不是说这是不好的,但是如果您增加复选框的数量和/或数据的复杂性,那么重新填充所有数据的工作量将急剧增加

唯一的问题是当窗口太小,无法支持大量的复选框(例如96个)时,您将不得不在另一个小部件而不是布局中组织复选框

下面是上面代码的一些截图: enter image description here

然后将self.num的值更改为6,并向其中添加一些随机数据:

self.add_data(x, np.sin(x)**2, 3)
self.add_data(x, np.cos(x)**2, 4)
self.add_data(x, x*0.2, 5)

enter image description here

相关问题 更多 >