Memperbarui plot secara dinamis di matplotlib

114

Saya membuat aplikasi dengan Python yang mengumpulkan data dari port serial dan memplot grafik dari data yang dikumpulkan terhadap waktu kedatangan. Waktu kedatangan data tidak pasti. Saya ingin plot diperbarui saat data diterima. Saya mencari cara melakukan ini dan menemukan dua metode:

  1. Kosongkan plot dan gambar kembali plot dengan semua poinnya lagi.
  2. Menganimasikan plot dengan mengubahnya setelah interval tertentu.

Saya tidak lebih suka yang pertama karena program berjalan dan mengumpulkan data untuk waktu yang lama (misalnya sehari), dan menggambar ulang plot akan sangat lambat. Yang kedua juga tidak disukai karena waktu kedatangan data tidak pasti dan saya ingin plot diperbarui hanya ketika data diterima.

Apakah ada cara di mana saya dapat memperbarui plot hanya dengan menambahkan lebih banyak poin ke dalamnya hanya ketika data diterima?

Shadman Anwer
sumber
2
Kemungkinan duplikat dari real-time plotting di while loop dengan matplotlib
Trevor Boyd Smith

Jawaban:

138

Apakah ada cara di mana saya dapat memperbarui plot hanya dengan menambahkan lebih banyak poin ke dalamnya ...

Ada beberapa cara untuk menganimasikan data di matplotlib, bergantung pada versi yang Anda miliki. Pernahkah Anda melihat contoh buku masak matplotlib ? Juga, lihat contoh animasi yang lebih modern di dokumentasi matplotlib. Terakhir, API animasi mendefinisikan fungsi FuncAnimation yang menganimasikan fungsi dalam waktu. Fungsi ini hanya bisa menjadi fungsi yang Anda gunakan untuk memperoleh data Anda.

Setiap metode pada dasarnya menyetel dataproperti objek yang sedang digambar, jadi tidak perlu membersihkan layar atau gambar. The dataproperti hanya dapat diperpanjang, sehingga Anda dapat menjaga poin sebelumnya dan hanya terus menambah baris Anda (atau gambar atau apapun yang Anda menggambar).

Mengingat bahwa Anda mengatakan bahwa waktu kedatangan data Anda tidak pasti, taruhan terbaik Anda mungkin hanya melakukan sesuatu seperti:

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

Kemudian ketika Anda menerima data dari port serial panggil saja update_line.

Chris
sumber
Akhirnya! Saya sudah mencari jawaban untuk +1 ini :) Bagaimana cara kita membuat plot ulang secara otomatis. ax.set_autoscale_on (True) sepertinya tidak berfungsi.
Edward Newell
13
Menemukan jawabannya: panggil ax.relim () lalu ax.autoscale_view () setelah memperbarui data tetapi sebelum memanggil plt.draw ()
Edward Newell
Tautan ke buku masak Matplotlib ( scipy.org/Cookbook/Matplotlib/Animations ) sepertinya rusak (Saya mendapatkan kesalahan "Terlarang")
David Doria
21
Karena tidak ada panggilan untuk menunjukkan (), plot tidak pernah muncul di layar. Jika saya memanggil show (), itu memblokir dan tidak melakukan pembaruan. Apakah saya melewatkan sesuatu? gist.github.com/daviddoria/027b5c158b6f200527a4
David Doria
2
tautkan ke jawaban mandiri yang serupa tetapi berbeda dengan kode yang dapat Anda jalankan (jawaban ini memiliki gagasan umum yang benar tetapi kode contoh tidak dapat dijalankan)
Trevor Boyd Smith
44

Untuk melakukan ini tanpa FuncAnimation (misalnya Anda ingin mengeksekusi bagian lain dari kode saat plot sedang diproduksi atau Anda ingin memperbarui beberapa plot pada saat yang sama), memanggil drawsaja tidak menghasilkan plot (setidaknya dengan qt backend).

Pekerjaan berikut untuk saya:

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()
Zah
sumber
Iya! Akhirnya solusi yang bekerja dengan Spyder! Hal yang saya lewatkan adalah gcf (). Canvas.flush_events () setelah perintah draw () -.
np8
Berdasarkan contoh yang bagus ini saya menulis modul Python kecil yang memungkinkan plot berulang: github.com/lorenzschmid/dynplot
lorenzli
1
Contoh yang bagus!
vvy
Jelas, ringkas, serba guna, fleksibel: ini harus menjadi jawaban yang diterima.
pfabri
Untuk menggunakan ini di Notebook Jupyter , Anda harus menambahkan %matplotlib notebookperintah ajaib setelah pernyataan import matplotlib Anda.
pfabri
3

Berikut adalah cara yang memungkinkan untuk menghilangkan titik setelah sejumlah titik diplot:

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()
NDM
sumber
2

Saya tahu saya terlambat menjawab pertanyaan ini, tetapi untuk masalah Anda, Anda dapat melihat ke dalam paket "joystick". Saya merancangnya untuk memplot aliran data dari port serial, tetapi berfungsi untuk aliran apa pun. Ini juga memungkinkan untuk pencatatan teks interaktif atau plot gambar (selain grafik plot). Tidak perlu melakukan loop Anda sendiri di utas terpisah, paket akan menanganinya, cukup berikan frekuensi pembaruan yang Anda inginkan. Ditambah terminal tetap tersedia untuk memantau perintah saat merencanakan. Lihat http://www.github.com/ceyzeriat/joystick/ atau https://pypi.python.org/pypi/joystick (gunakan joystick pip install untuk menginstal)

Cukup ganti np.random.random () dengan titik data asli Anda yang dibaca dari port serial pada kode di bawah ini:

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()
Guillaume S
sumber