如何更新pyqtgraph中的绘图? [英] How to update a plot in pyqtgraph?

查看:76
本文介绍了如何更新pyqtgraph中的绘图?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

我有两个问题:

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

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

代码是:

from PyQt5 import QtWidgets, uic, QtGui导入 matplotlib.pyplot 作为 plt从 matplotlib.widgets 导入 SpanSelector将 numpy 导入为 np导入系统导入字符串将 pyqtgraph 导入为 pg从 pyqtgraph.Qt 导入 QtGui、QtCoreapp = QtWidgets.QApplication(sys.argv)x = np.linspace(0, 3.14, 100)y1 = np.sin(x)#与复选框A1关联的数据编号1y2 = np.cos(x)#与复选框A2关联的数据编号2#每当复选框的状态改变时调用此函数定义待办事项():如果 cbx1.isChecked():全局曲线1曲线1 = plot.plot(x, y1, pen = 'r')别的:尝试:plot.removeItem(curve1)除了名称错误:经过如果 cbx2.isChecked():全局曲线2曲线2 = plot.plot(x, y2, pen = 'y')别的:尝试:plot.removeItem(曲线2)除了名称错误:经过#一个小部件来保存我所有未来的小部件widget_holder = QtGui.QWidget()#复选框名为 A1 和 A2cbx1 = QtWidgets.QCheckBox()cbx1.setText('A1')cbx1.stateChanged.connect(todo)cbx2 = QtWidgets.QCheckBox()cbx2.setText('A2')cbx2.stateChanged.connect(todo)#制作一个pyqtgraph绘图小部件绘图 = pg.PlotWidget()#设置布局布局 = QtGui.QGridLayout()widget_holder.setLayout(布局)#将小部件添加到布局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).

导入系统将 numpy 导入为 np将 pyqtgraph 导入为 pg从 pyqtgraph.Qt 导入 QtCore、QtGui类 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)# 让我们在里面创建一些小部件self.label = QtGui.QLabel('Plots and Checkbox bellow:')# 这是来自 pyqtgraph 的绘图小部件self.plot_widget = pg.PlotWidget()# 现在是复选框(让我们制作其中的 3 个)self.num = 6self.check_boxes = [QtGui.QCheckBox(f"Box {i+1}") for i in range(self.num)]# 这里将是绘图的数据self.plot_data = [范围内的_无(self.num)]# 现在我们构建整个 GUIself.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)对于我在范围内(self.num):self.boxes_layout.addWidget(self.check_boxes[i])# 这会将每个框连接到相同的操作self.check_boxes[i].stateChanged.connect(self.box_changed)# 为了优化,让我们创建一个包含盒子状态的列表self.state = [False for _ in range(self.num)]# 做一个list,保存每个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]如果 self.plot_data[ind] 不是 None:self.plot_data[ind].setData(x, y)def box_changed(self):对于我在范围内(self.num):如果 self.check_boxes[i].isChecked() != self.state[i]:self.state[i] = self.check_boxes[i].isChecked()如果 self.state[i]:如果 self.plot_data[i] 不是 None:self.plot_widget.addItem(self.plot_data[i])别的:self.plot_data[i] = self.plot_widget.plot(*self.box_data[i])别的:self.plot_widget.removeItem(self.plot_data[i])休息如果 __name__ == __main__":app = QtGui.QApplication(sys.argv)窗口 = MyApp()window.show()sys.exit(app.exec_())

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

正如我在开头所说的,这对于很多复选框应该可以正常工作,因为在复选框处于选中/未选中状态时调用的函数,首先将每个框的实际状态与前一个(存储在 self.state) 并且只对与该特定框对应的图进行更改.有了这个,您就可以避免在每次选中/取消选中一个框时为每个复选框执行一个函数以及重新绘制所有 de 框(例如

然后将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)

I am trying to have a user interface using PyQt5 and 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. There are two checkboxes with texts A1 and A2 and each of them plot one set of data.

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. However, If I check A1 box and then I check A2 box, then deselecting A1 does not clear the associated 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.

2- The real application have 96 buttons each of which should be associated to one data set. 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. 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).

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. With this, you can easily remove it from the Plot Widget and add it again. 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

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

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

这篇关于如何更新pyqtgraph中的绘图?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆