什么是wxPython中最好的实时绘图小部件?

15

我想使用Python和wxPython显示一个实时曲线图,其中包括一两条曲线以及每秒高达50个样本。

该小部件应支持Win32和Linux平台。

欢迎任何提示。

编辑后添加:

我不需要在50fps下更新显示,但是需要在两条曲线上显示最多50个数据样本,并具有合理的显示更新速率(5..10 fps应该可以)。

编辑后添加:

我已经在一个项目中成功地使用了mathplotlib。 然后我为其他项目选择了wx.lib.plot,我发现它更简单,更易于使用,并且消耗的CPU周期较少。由于wx.lib作为标准wxPython发行版的一部分出现,因此特别容易使用。

5个回答

16
如果您想要高性能和最小的代码占用空间,那么请看Python内置的绘图库tkinter。不需要编写特殊的C / C++代码或使用大型绘图包即可获得比50 fps更好的性能。

Screenshot

以下代码可以在2.2 GHz Core 2 duo上以400 fps的速度滚动1000x200条形图,在3.4 GHz Core i3上则可达到1000 fps。中心程序"scrollstrip"在右侧绘制一组数据点和相应的颜色,以及一个可选的垂直网格线,然后将条形图向左滚动1个单位。要绘制水平网格线,只需将其包含在数据和颜色数组中作为常量,与变量数据点一起使用即可。
from tkinter import *
import math, random, threading, time

class StripChart:

    def __init__(self, root):
        self.gf = self.makeGraph(root)
        self.cf = self.makeControls(root)
        self.gf.pack()
        self.cf.pack()
        self.Reset()

    def makeGraph(self, frame):
        self.sw = 1000
        self.h = 200
        self.top = 2
        gf = Canvas(frame, width=self.sw, height=self.h+10,
                    bg="#002", bd=0, highlightthickness=0)
        gf.p = PhotoImage(width=2*self.sw, height=self.h)
        self.item = gf.create_image(0, self.top, image=gf.p, anchor=NW)
        return(gf)

    def makeControls(self, frame):
        cf = Frame(frame, borderwidth=1, relief="raised")
        Button(cf, text="Run", command=self.Run).grid(column=2, row=2)
        Button(cf, text="Stop", command=self.Stop).grid(column=4, row=2)
        Button(cf, text="Reset", command=self.Reset).grid(column=6, row=2)
        self.fps = Label(cf, text="0 fps")
        self.fps.grid(column=2, row=4, columnspan=5)
        return(cf)

    def Run(self):
        self.go = 1
        for t in threading.enumerate():
            if t.name == "_gen_":
                print("already running")
                return
        threading.Thread(target=self.do_start, name="_gen_").start()

    def Stop(self):
        self.go = 0
        for t in threading.enumerate():
            if t.name == "_gen_":
                t.join()

    def Reset(self):
        self.Stop()
        self.clearstrip(self.gf.p, '#345')

    def do_start(self):
        t = 0
        y2 = 0
        tx = time.time()
        while self.go:
            y1 = 0.2*math.sin(0.02*math.pi*t)
            y2 = 0.9*y2 + 0.1*(random.random()-0.5)
            self.scrollstrip(self.gf.p,
               (0.25+y1,   0.25, 0.7+y2,   0.6,     0.7,   0.8),
               ( '#ff4', '#f40', '#4af', '#080', '#0f0', '#080'),
                 "" if t % 65 else "#088")

            t += 1
            if not t % 100:
                tx2 = time.time()
                self.fps.config(text='%d fps' % int(100/(tx2 - tx)))
                tx = tx2
#            time.sleep(0.001)

    def clearstrip(self, p, color):  # Fill strip with background color
        self.bg = color              # save background color for scroll
        self.data = None             # clear previous data
        self.x = 0
        p.tk.call(p, 'put', color, '-to', 0, 0, p['width'], p['height'])

    def scrollstrip(self, p, data, colors, bar=""):   # Scroll the strip, add new data
        self.x = (self.x + 1) % self.sw               # x = double buffer position
        bg = bar if bar else self.bg
        p.tk.call(p, 'put', bg, '-to', self.x, 0,
                  self.x+1, self.h)
        p.tk.call(p, 'put', bg, '-to', self.x+self.sw, 0,
                  self.x+self.sw+1, self.h)
        self.gf.coords(self.item, -1-self.x, self.top)  # scroll to just-written column
        if not self.data:
            self.data = data
        for d in range(len(data)):
            y0 = int((self.h-1) * (1.0-self.data[d]))   # plot all the data points
            y1 = int((self.h-1) * (1.0-data[d]))
            ya, yb = sorted((y0, y1))
            for y in range(ya, yb+1):                   # connect the dots
                p.put(colors[d], (self.x,y))
                p.put(colors[d], (self.x+self.sw,y))
        self.data = data            # save for next call

def main():
    root = Tk()
    root.title("StripChart")
    app = StripChart(root)
    root.mainloop()

main()

这非常聪明。 :-) 谢谢。 - iaswtw

6
创建一个能够从你的数据源读取并以50FPS真正更新的C++小部件并不困难。这种方法的美妙之处在于,在50FPS下几乎没有(如果有的话)Python代码会被执行,它们都在C++中,具体取决于你如何将更新的数据传递给小部件。
你甚至可以从Python侧向自定义实时数据查看器中推入事件处理程序,以处理所有鼠标事件和用户交互,并仅在C++中保留渲染。
这将是一个扩展wxWidget的wxWindow类的小型C++类。
class RealtimeDataViewer: public wxWindow { ...
并重写OnPaint
void OnPaint(wxPaintEvent &WXUNUSED(event)) { ....
然后它将获得设备上下文,并开始绘制线条和形状...
接下来,你需要将.h文件复制到.i,并稍微调整一下,使其成为SWIG可用于扩展wxPython的定义。
使用以下参数设置,构建过程可以由Python自己的distutils处理:
  ext_modules=[Extension('myextension', sources, 
                          include_dirs=includeDirs
                          library_dirs=usual_libs,
                          )],

将其变得美观且运行良好可能需要几天时间......但这可能是真正加速您的项目进入未来的选项之一。

而且所有这些都可以在Mac、Windows和Linux上很好地工作。

wxPython确实是一个隐藏的宝石,如果有更多专业支持的IDE/设计师工具,它将真正占领世界。

话虽如此,首先尝试matplotlib,它有许多优美的优化渲染,并且也可以实时更新。


谢谢。实际上我想避免使用C++来完成这个任务。我不需要50帧每秒的更新速率,但我希望以合理的更新速率(例如10fps或甚至5fps)显示50个样本/秒。 - Ber
1
只要您知道这个选项的存在...我认为matplotlib会完美地满足您当前的需求。 - Jim Carroll

2

1
我使用PyQtGraph来完成这种任务。它比Matplotlib更快,适用于实时绘图,并且具有许多方便的功能,例如在绘图画布中的上下文菜单,自动缩放和滚动而无需额外的工作。

0
也许可以考虑使用Chaco?我不确定它是否能够做到每秒50帧,但我在演示中看到它可以非常流畅地实时绘图。它肯定比matplotlib快。

Chaco是一个功能强大的绘图API,具有许多特性(如图形中的箭头、标记等)。它也可以在wxPython中使用。 - sancelot

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接