简体   繁体   English

如何在 pyqtgraph 中更新 plot?

[英]How to update a plot in pyqtgraph?

I am trying to have a user interface using PyQt5 and pyqtgraph.我正在尝试使用 PyQt5 和 pyqtgraph 的用户界面。 I made two checkboxes and whenever I select them I want to plot one of the two data sets available in the code and whenever I deselect a button I want it to clear the corresponding curve.我做了两个复选框,每当我 select 它们时,我想 plot 代码中可用的两个数据集之一,每当我取消选择一个按钮时,我希望它清除相应的曲线。 There are two checkboxes with texts A1 and A2 and each of them plot one set of data.有两个带有文本A1A2的复选框,每个复选框 plot 一组数据。

I have two issues:我有两个问题:

1- If I select A1 it plots the data associated with A1 and as long as I do not select A2 , by deselecting A1 I can clear the data associated with A1 . 1-如果我 select A1它绘制与A1关联的数据,只要我不 select A2 ,通过取消选择A1我可以清除与A1关联的数据。 However, If I check A1 box and then I check A2 box, then deselecting A1 does not clear the associated plot.但是,如果我选中A1框,然后选中A2框,则取消选择A1不会清除关联的 plot。 In this situation, if I choose to plot random data, instead of a deterministic curve such as sin , I see that by selecting either button new data is added but it cannot be removed.在这种情况下,如果我选择 plot 随机数据,而不是诸如sin之类的确定性曲线,我会看到通过选择任一按钮添加了新数据,但无法将其删除。

2- The real application have 96 buttons each of which should be associated to one data set. 2- 真正的应用程序有 96 个按钮,每个按钮都应该与一个数据集相关联。 I think the way I wrote the code is inefficient because I need to copy the same code for one button and data set 96 times.我认为我编写代码的方式效率低下,因为我需要为一个按钮和数据集复制相同的代码 96 次。 Is there a way to generalize the toy code I presented below to arbitrary number of checkboxes?有没有办法将我在下面介绍的玩具代码推广到任意数量的复选框? Or perhaps, using/copying the almost the same code for every button is the usual and correct way to do this?或者,也许,为每个按钮使用/复制几乎相同的代码是通常且正确的方法?

The code is:代码是:

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_())


    

Below is an example I made that works fine.下面是我制作的一个很好的例子。 It can be reused to do more plots without increasing the code, just changing the value of self.num and adding the corresponding data using the function add_data(x,y,ind) , where x and y are the values of the data and ind is the index of the box (from 0 to n-1 ).可以重复使用它来做更多的绘图而不增加代码,只需更改self.num的值并使用 function 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_())

Note that inside de PlotWidget I add the plot using the plot() method, it returns a PlotDataItem that is saved in the list created before called self.plot_data .请注意,在 de PlotWidget中,我使用plot()方法添加了 plot ,它返回一个PlotDataItem ,它保存在之前创建的名为self.plot_data的列表中。 With this, you can easily remove it from the Plot Widget and add it again.有了这个,您可以轻松地将其从Plot Widget中删除并重新添加。 Also if you are aiming for a more complex program, for example, one that you can change the data of each box on the run, the plot will update without major issues if you use the setData() method on the PlotDataItem此外,如果您的目标是更复杂的程序,例如,您可以在运行时更改每个框的数据,如果您使用 PlotDataItem 上的setData()方法, PlotDataItem将更新而不会出现重大问题

As I said at the beginning, this should work fine with a lot of checkboxes, because the function that is called when a checkbox is Checked/Unchecked, first compare the actual state of each box with the previous one (stored in self.state ) and only do the changes on the plot corresponding to that specific box.正如我在开始时所说,这应该适用于很多复选框,因为选中/取消选中复选框时调用的 function,首先将每个框的实际 state 与前一个框进行比较(存储在self.state中)并且只对与该特定框相对应的 plot 进行更改。 With this, you avoid doing one function for each checkbox and the replot of all de boxes every time you check/uncheck a box (like user8408080 did).这样,您就可以避免为每个复选框执行一个 function 并在每次选中/取消选中一个框时重新绘制所有 de 框(就像user8408080所做的那样)。 I don't say it is bad, but if you increase the number of checkboxes and/or the complexity of the data, the workload of replotting all of the data will increase drastically.我并不是说这很糟糕,但是如果您增加复选框的数量和/或数据的复杂性,重新绘制所有数据的工作量将急剧增加。

The only problem will be when the window is too small to support a crazy amount of checkboxes (96 for example), then you will have to organize the checkboxes in another widget instead of a layout.唯一的问题是当 window 太小而无法支持大量的复选框(例如 96 个)时,您将不得不在另一个小部件而不是布局中组织复选框。

Now some screenshots of the code from above:现在是上面代码的一些截图: 在此处输入图像描述

And then changing the value of self.num to 6 and adding some random data to them:然后将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)

在此处输入图像描述

In the following I take a more brute force approach, while assuming, that plotting all the curves takes an negligible amount of time:在下文中,我采用了一种更强力的方法,同时假设绘制所有曲线所需的时间可以忽略不计:

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_())

Now you have a list of checkboxes and the checkbox with index 0 corresponds to the data in the curves -list with index 0. I plot all the curves everytime, which yields a little bit more readable code.现在你有一个复选框列表,索引为 0 的复选框对应于curves列表中的数据,索引为 0。我 plot 每次都会产生所有曲线,这会产生更易读的代码。 If this does affect performance, though, this needs to be a little more complicated.但是,如果这确实会影响性能,则需要稍微复杂一些。

I also tried to add another curve and it seems to work out perfectly fine:我还尝试添加另一条曲线,看起来效果很好:

具有三条曲线的 Img

I found the problem in your code.我在您的代码中发现了问题。 Let's see what your code does:让我们看看你的代码做了什么:

  1. When you add the first plot to the widget (either A1 or A2 ) you get the PlotDataItem and store it in curve1 or curve2 .当您将第一个 plot 添加到小部件( A1A2 )时,您将获得PlotDataItem并将其存储在curve1curve2中。 Suppose you check first A1 , then your todo function first inspects that the checkbox 1 is Checked, so plot the data and store it in curve1 , then the same function inspects the checkbox 2. Checkbox 2 is not checked so the function does the else statement, which removes the curve2 from the plot widget, this variable doesn't exist so it might raise an error, however, you use the try statement and the error never raises. Suppose you check first A1 , then your todo function first inspects that the checkbox 1 is Checked, so plot the data and store it in curve1 , then the same function inspects the checkbox 2. Checkbox 2 is not checked so the function does the else statement ,它从 plot 小部件中删除了curve2 ,此变量不存在,因此它可能会引发错误,但是,您使用try语句并且永远不会引发错误。

  2. Now, you check the A2 box, your function first inspects checkbox 1, it is checked, so the function will add again the same plot, but as another PlotDataItem , and store it in curve1 .现在,您选中A2框,您的 function 首先检查复选框 1,它已被选中,因此 function 将再次添加相同的PlotDataItem ,但将其存储在另一个curve1中。 Until now, you have two PlotDataItem of the same data (that means two plots) but only the last one is stored in curve1 .到目前为止,您有两个相同数据的PlotDataItem (这意味着两个图),但只有最后一个存储在curve1中。 The next thing the function does is inspect checkbox 2, it is checked so it will plot the second data and save its PlotDataItem in curve2 function 做的下一件事是检查复选框 2,它被选中,因此它将 plot 第二个数据并将其PlotDataItem保存在curve2

  3. So, when you now uncheck checkbox 1, your function first inspects checkbox 1 (sorry if it is repetitive), it is unchecked, so the function will remove the PlotDataItem stored in curve1 and it does it, but remember you have two plots of the same data, so for us (the viewers) the plot doesn't disappear.因此,当您现在取消选中复选框 1 时,您的 function 首先检查复选框 1(抱歉,如果它是重复的),它未选中,因此PlotDataItem将删除存储在曲线 1 中的curve1并且它会删除它,但请记住您有两个图相同的数据,所以对我们(观众)来说 plot 不会消失。 That is the problem, but it doesn't end there, the function now inspects checkbox 2, it is checked, so the function will add another PlotDataItem of the second data and stores it in curve2 .这就是问题所在,但还不止于此, function 现在检查复选框 2,它已被选中,因此 function 将添加第二个数据的另一个PlotDataItem并将其存储在curve2中。 We again will have the same problem that happened to the first data.我们将再次遇到与第一个数据相同的问题。

With this analysis, I also learned something, the PlotDataItem doesn´t disappear if you "overwrite" the variable in which it is stored, neither it does when it is removed from the PlotWidget .通过这个分析,我还学到了一些东西,如果你“覆盖”存储它的变量, PlotDataItem不会消失,当它从PlotWidget中删除时也不会消失。 Considering that, I did some changes to the code of my previous answer because the old code will create another item each time we check a box that was checked before and was unchecked.考虑到这一点,我对之前答案的代码进行了一些更改,因为每次我们选中之前选中但未选中的框时,旧代码都会创建另一个项目。 Now, if the item is created, my function will add it again, instead of creating another one.现在,如果创建了该项目,我的 function 将再次添加它,而不是创建另一个。

I have some suggestions:我有一些建议:

  • Try using objects, generate your own widget class.尝试使用对象,生成您自己的小部件 class。 You can avoid calling global variables, passing them as attributes of the class.您可以避免调用全局变量,将它们作为 class 的属性传递。 (Like my previous answer) (就像我之前的回答一样)

  • If you want to maintain your code as it is (without the use of classes), for it to work, you can add another two variables with the "state" of your checkboxes, so when you call your function first it checks if the state didn´t change and ignore that checkbox.如果你想保持你的代码原样(不使用类),为了让它工作,你可以添加另外两个变量和你的复选框的“状态”,所以当你首先调用你的 function 时,它会检查 state没有更改并忽略该复选框。 Also, check if the PlotDataItem was generated before and only add it again to avoid the generation of more items.另外,检查PlotDataItem之前是否生成过,然后再添加一次,以避免生成更多项目。

  • Your objective is to do this with a bunch of boxes or buttons, try using only one variable for all of them: for example, a list, containing all of the boxes/buttons (the objects).您的目标是使用一堆框或按钮来执行此操作,尝试对所有这些框仅使用一个变量:例如,包含所有框/按钮(对象)的列表。 Then you can manage any of them by the index.然后你可以通过索引来管理它们中的任何一个。 Also, you can do loops over that variable for connecting the objects inside to the same function.此外,您可以对该变量进行循环,以将内部的对象连接到同一个 function。

     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)
  • Doing lists of objects also helps you to give names automatically easily:列出对象还可以帮助您轻松地自动命名:

     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) ]

Finally, here is your code with some small changes to make it work:最后,这是您的代码,并进行了一些小改动以使其正常工作:

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_())

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM