Tự động cập nhật cốt truyện trong matplotlib


114

Tôi đang tạo một ứng dụng bằng Python để thu thập dữ liệu từ một cổng nối tiếp và vẽ biểu đồ của dữ liệu đã thu thập theo thời gian đến. Thời gian đến của dữ liệu là không chắc chắn. Tôi muốn cốt truyện được cập nhật khi nhận được dữ liệu. Tôi đã tìm kiếm cách thực hiện việc này và tìm thấy hai phương pháp:

  1. Xóa cốt truyện và vẽ lại cốt truyện với tất cả các điểm một lần nữa.
  2. Tạo hoạt ảnh cho cốt truyện bằng cách thay đổi nó sau một khoảng thời gian cụ thể.

Tôi không thích phần đầu tiên vì chương trình chạy và thu thập dữ liệu trong một thời gian dài (một ngày chẳng hạn) và việc vẽ lại cốt truyện sẽ khá chậm. Cách thứ hai cũng không thích hợp hơn vì thời gian dữ liệu đến là không chắc chắn và tôi muốn cốt truyện chỉ cập nhật khi dữ liệu được nhận.

Có cách nào mà tôi có thể cập nhật cốt truyện chỉ bằng cách thêm nhiều điểm vào nó chỉ khi nhận được dữ liệu không?


Câu trả lời:


138

Có cách nào để tôi có thể cập nhật cốt truyện chỉ bằng cách thêm [các] điểm vào nó ...

Có một số cách tạo hoạt ảnh cho dữ liệu trong matplotlib, tùy thuộc vào phiên bản bạn có. Bạn đã xem các ví dụ về sách nấu ăn matplotlib chưa? Ngoài ra, hãy xem các ví dụ hoạt hình hiện đại hơn trong tài liệu matplotlib. Cuối cùng, API hoạt ảnh định nghĩa một hàm FuncAnimation để làm hoạt ảnh một hàm theo thời gian. Chức năng này chỉ có thể là chức năng bạn sử dụng để lấy dữ liệu của mình.

Mỗi phương thức về cơ bản đặt thuộc datatính của đối tượng được vẽ, do đó không yêu cầu xóa màn hình hoặc hình vẽ. Các databất động sản chỉ có thể được mở rộng, vì vậy bạn có thể giữ điểm trước và chỉ cần tiếp tục bổ sung vào dòng của bạn (hoặc hình ảnh hoặc bất cứ điều gì bạn đang vẽ).

Giả sử rằng bạn nói rằng thời gian đến dữ liệu của bạn là không chắc chắn, đặt cược tốt nhất của bạn có lẽ chỉ là làm điều gì đó như:

import matplotlib.pyplot as plt
import numpy

hl, = plt.plot([], [])

def update_line(hl, new_data):
    hl.set_xdata(numpy.append(hl.get_xdata(), new_data))
    hl.set_ydata(numpy.append(hl.get_ydata(), new_data))
    plt.draw()

Sau đó, khi bạn nhận được dữ liệu từ cổng nối tiếp chỉ cần gọi update_line.


Cuối cùng! Tôi đang tìm câu trả lời cho +1 này :) Làm cách nào để chúng tôi tự động bán lại cốt truyện. ax.set_autoscale_on (True) dường như không hoạt động.
Edward Newell

13
Đã tìm thấy câu trả lời: gọi ax.relim () rồi đến ax.autoscale_view () sau khi cập nhật dữ liệu nhưng trước khi gọi plt.draw ()
Edward Newell

Các liên kết đến các sách dạy nấu ăn Matplotlib ( scipy.org/Cookbook/Matplotlib/Animations ) dường như bị phá vỡ (tôi nhận được một lỗi "Forbidden")
David Doria

21
Vì không có lệnh gọi hiển thị (), cốt truyện không bao giờ xuất hiện trên màn hình. Nếu tôi gọi show (), nó sẽ chặn và không thực hiện cập nhật. Tui bỏ lỡ điều gì vậy? gist.github.com/daviddoria/027b5c158b6f200527a4
David Doria

2
liên kết đến một tương tự nhưng khác nhau khép kín câu trả lời với mã mà bạn có thể chạy (câu trả lời này có ý tưởng chung đúng nhưng mã ví dụ không thể chạy)
Trevor Boyd Smith

44

Để thực hiện điều này mà không có FuncAnimation (ví dụ: bạn muốn thực thi các phần khác của mã trong khi cốt truyện đang được tạo hoặc bạn muốn cập nhật một số ô cùng một lúc), việc gọi drawmột mình không tạo ra cốt truyện (ít nhất là với phụ trợ qt).

Những điều sau đây phù hợp với tôi:

import matplotlib.pyplot as plt
plt.ion()
class DynamicUpdate():
    #Suppose we know the x range
    min_x = 0
    max_x = 10

    def on_launch(self):
        #Set up plot
        self.figure, self.ax = plt.subplots()
        self.lines, = self.ax.plot([],[], 'o')
        #Autoscale on unknown axis and known lims on the other
        self.ax.set_autoscaley_on(True)
        self.ax.set_xlim(self.min_x, self.max_x)
        #Other stuff
        self.ax.grid()
        ...

    def on_running(self, xdata, ydata):
        #Update data (with the new _and_ the old points)
        self.lines.set_xdata(xdata)
        self.lines.set_ydata(ydata)
        #Need both of these in order to rescale
        self.ax.relim()
        self.ax.autoscale_view()
        #We need to draw *and* flush
        self.figure.canvas.draw()
        self.figure.canvas.flush_events()

    #Example
    def __call__(self):
        import numpy as np
        import time
        self.on_launch()
        xdata = []
        ydata = []
        for x in np.arange(0,10,0.5):
            xdata.append(x)
            ydata.append(np.exp(-x**2)+10*np.exp(-(x-7)**2))
            self.on_running(xdata, ydata)
            time.sleep(1)
        return xdata, ydata

d = DynamicUpdate()
d()

Đúng! Cuối cùng là một giải pháp hoạt động với Spyder! Điều tôi thiếu là gcf (). Canvas.flush_events () sau lệnh draw () -.
np8

Dựa trên ví dụ tuyệt vời này, tôi đã viết một mô-đun Python nhỏ cho phép dùng cho vẽ lặp đi lặp lại: github.com/lorenzschmid/dynplot
lorenzli

1
Một ví dụ đẹp!
vvy

Rõ ràng, ngắn gọn, linh hoạt, linh hoạt: đây phải là câu trả lời được chấp nhận.
pfabri

Để sử dụng điều này trong Máy tính xách tay Jupyter , bạn phải thêm %matplotlib notebooklệnh ma thuật sau câu lệnh nhập matplotlib của mình.
pfabri

3

Đây là một cách cho phép xóa điểm sau một số điểm nhất định được vẽ:

import matplotlib.pyplot as plt
# generate axes object
ax = plt.axes()

# set limits
plt.xlim(0,10) 
plt.ylim(0,10)

for i in range(10):        
     # add something to axes    
     ax.scatter([i], [i]) 
     ax.plot([i], [i+1], 'rx')

     # draw the plot
     plt.draw() 
     plt.pause(0.01) #is necessary for the plot to update for some reason

     # start removing points if you don't want all shown
     if i>2:
         ax.lines[0].remove()
         ax.collections[0].remove()

2

Tôi biết mình đã muộn để trả lời câu hỏi này, nhưng đối với vấn đề của bạn, bạn có thể xem xét gói "cần điều khiển". Tôi đã thiết kế nó để vẽ một luồng dữ liệu từ cổng nối tiếp, nhưng nó hoạt động cho bất kỳ luồng nào. Nó cũng cho phép ghi nhật ký văn bản tương tác hoặc vẽ biểu đồ hình ảnh (ngoài việc vẽ biểu đồ). Không cần phải thực hiện các vòng lặp của riêng bạn trong một chuỗi riêng biệt, gói sẽ đảm nhiệm việc đó, chỉ cần cung cấp tần suất cập nhật bạn muốn. Thêm vào đó, thiết bị đầu cuối vẫn có sẵn để giám sát các lệnh trong khi vẽ biểu đồ. Xem http://www.github.com/ceyzeriat/joystick/ hoặc https://pypi.python.org/pypi/joystick (sử dụng cần điều khiển pip install để cài đặt)

Chỉ cần thay thế np.random.random () bằng điểm dữ liệu thực của bạn được đọc từ cổng nối tiếp trong mã bên dưới:

import joystick as jk
import numpy as np
import time

class test(jk.Joystick):
    # initialize the infinite loop decorator
    _infinite_loop = jk.deco_infinite_loop()

    def _init(self, *args, **kwargs):
        """
        Function called at initialization, see the doc
        """
        self._t0 = time.time()  # initialize time
        self.xdata = np.array([self._t0])  # time x-axis
        self.ydata = np.array([0.0])  # fake data y-axis
        # create a graph frame
        self.mygraph = self.add_frame(jk.Graph(name="test", size=(500, 500), pos=(50, 50), fmt="go-", xnpts=10000, xnptsmax=10000, xylim=(None, None, 0, 1)))

    @_infinite_loop(wait_time=0.2)
    def _generate_data(self):  # function looped every 0.2 second to read or produce data
        """
        Loop starting with the simulation start, getting data and
    pushing it to the graph every 0.2 seconds
        """
        # concatenate data on the time x-axis
        self.xdata = jk.core.add_datapoint(self.xdata, time.time(), xnptsmax=self.mygraph.xnptsmax)
        # concatenate data on the fake data y-axis
        self.ydata = jk.core.add_datapoint(self.ydata, np.random.random(), xnptsmax=self.mygraph.xnptsmax)
        self.mygraph.set_xydata(t, self.ydata)

t = test()
t.start()
t.stop()
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.