16

I would like to show a real time graph with one or two curves an up to 50 samples per second using Python and wxPython. The widget should support both Win32 and Linux platforms.

Any hints are welcome.

Edited to add:

I don't need to update the display at 50 fps, but up need to show up to 50 samples of data on both curves, with a reasonable update rate for the display (5..10 fps should be okay).

Edited to add:

I have used mathplotlib in a project with good success. I have then settled for wx.lib.plot for other projects, which I found to be simpler, but somewhat easier to use and consuming less CPU cycles. As wx.lib comes as part of the standard wxPython distribution is is particularly easy to use.

4

5 回答 5

16

如果您想以最少的代码占用量获得高性能,那么看看 Python 的内置绘图库 tkinter。无需编写特殊的 C/C++ 代码或使用大型绘图包即可获得优于 50 fps 的性能。

截屏

以下代码在 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()
于 2011-09-30T01:24:00.377 回答
6

创建一个可以从您的数据源读取并真正以 50 FPS 更新的 C++ 小部件并不难。这种方法的美妙之处在于,很少(如果有的话)Python 代码将以 50FPS 的速度执行,它们都在 C++ 中,这取决于您如何将更新的数据传递给小部件。

您甚至可以从 Python 端将事件处理程序推送到自定义实时数据查看器中,以处理所有鼠标事件和用户交互,而只保留 C++ 中的呈现。

这将是一个扩展 wxWidget 的 wxWindow 类的小型 C++ 类

类 RealtimeDataViewer: 公共 wxWindow { ...

并覆盖 OnPaint

无效 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,它有很多漂亮的优化渲染,而且还可以实时更新。

于 2009-01-19T14:40:50.027 回答
2

如果你真的想要每秒 50 帧的快速东西,我认为你需要像 PyGame 这样的东西,并直接与显示器对话,而不是绘图模块。

检查相关线程:

于 2009-01-19T12:57:15.197 回答
1

我将PyQtGraph用于这种事情。它在实时绘图方面比 Matplotlib 快得多,并且具有许多不错的便利功能,例如绘图画布中的上下文菜单,具有自动缩放和滚动功能,无需任何额外工作。

于 2016-09-30T05:21:06.350 回答
0

Maybe Chaco? I don't know if it can do 50 frames per second, but I saw in a demonstration how it did very smooth realtime plotting. It should definitely be faster than matplotlib.

于 2009-01-19T11:27:17.780 回答