Dynamiczna aktualizacja działki w matplotlib

114

Tworzę aplikację w Pythonie, która zbiera dane z portu szeregowego i rysuje wykres zebranych danych względem czasu przybycia. Czas przybycia danych jest niepewny. Chcę, aby fabuła była aktualizowana po otrzymaniu danych. Szukałem, jak to zrobić i znalazłem dwie metody:

  1. Wyczyść wykres i ponownie narysuj wykres ze wszystkimi punktami.
  2. Animuj wykres, zmieniając go po określonym czasie.

Nie preferuję pierwszego, ponieważ program działa i zbiera dane przez długi czas (na przykład dzień), a przerysowanie wykresu będzie dość powolne. Drugi również nie jest preferowany, ponieważ czas nadejścia danych jest niepewny i chcę, aby wykres aktualizował się tylko po otrzymaniu danych.

Czy istnieje sposób, w jaki mogę zaktualizować wykres, dodając do niego więcej punktów tylko po otrzymaniu danych?

Shadman Anwer
źródło
2
Możliwy duplikat kreślenia w czasie rzeczywistym w pętli while z matplotlib
Trevor Boyd Smith

Odpowiedzi:

138

Czy istnieje sposób, w jaki mogę zaktualizować fabułę, dodając do niej więcej punktów ...

Istnieje wiele sposobów animowania danych w matplotlib, w zależności od posiadanej wersji. Czy widziałeś przykłady książek kucharskich matplotlib ? Sprawdź również bardziej nowoczesne przykłady animacji w dokumentacji matplotlib. Wreszcie interfejs API animacji definiuje funkcję FuncAnimation, która animuje funkcję w czasie. Ta funkcja może być po prostu funkcją, której używasz do pozyskiwania danych.

Każda metoda zasadniczo ustawia datawłaściwość rysowanego obiektu, więc nie wymaga czyszczenia ekranu ani figury. dataNieruchomość może być po prostu przedłużony, dzięki czemu można zachować dotychczasowe punkty i po prostu dodanie do linii (lub obraz lub czymkolwiek jesteś rysunek).

Biorąc pod uwagę, że mówisz, że czas nadejścia danych jest niepewny, najlepszym rozwiązaniem jest prawdopodobnie zrobienie czegoś takiego:

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

Wtedy gdy otrzymasz dane z portu szeregowego po prostu zadzwoń update_line.

Chris
źródło
Wreszcie! Szukałem odpowiedzi na to +1 :) Jak automatycznie przeskalować działkę. ax.set_autoscale_on (True) nie działa.
Edward Newell
13
Znalazłem odpowiedź: wywołaj ax.relim (), a następnie ax.autoscale_view () po zaktualizowaniu danych, ale przed wywołaniem plt.draw ()
Edward Newell
Link do książki kucharskiej Matplotlib ( scipy.org/Cookbook/Matplotlib/Animations ) wydaje się być uszkodzony (pojawia się błąd „Zabroniony”)
David Doria
21
Ponieważ nie ma wywołania show (), wykres nigdy nie pojawia się na ekranie. Jeśli wywołam show (), blokuje i nie wykonuje aktualizacji. Czy coś mi brakuje? gist.github.com/daviddoria/027b5c158b6f200527a4
David Doria
2
link do podobnej, ale innej samodzielnej odpowiedzi z kodem, który można uruchomić (ta odpowiedź ma dobry ogólny pomysł, ale przykładowego kodu nie można uruchomić)
Trevor Boyd Smith
44

Aby to zrobić bez FuncAnimation (np. Chcesz wykonać inne części kodu podczas tworzenia wykresu lub chcesz zaktualizować kilka wykresów w tym samym czasie), drawsamo wywołanie nie generuje wykresu (przynajmniej z qt backend).

U mnie działa:

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
źródło
Tak! Wreszcie rozwiązanie, które działa ze Spyderem! Brakowało mi gcf (). Canvas.flush_events () po poleceniu draw () -.
np8
W oparciu o ten wielki przykład napisałem mały moduł Pythona pozwalające na powtarzalnym wydrukiem: github.com/lorenzschmid/dynplot
lorenzli
1
Piękny przykład!
vvy
Jasna, zwięzła, wszechstronna, elastyczna: taka powinna być akceptowana odpowiedź.
pfabri
Aby użyć tego w notatniku Jupyter , musisz dodać %matplotlib notebookmagiczne polecenie po instrukcji importu matplotlib.
pfabri
3

Oto sposób, który pozwala usunąć punkty po wykreśleniu określonej liczby punktów:

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
źródło
2

Wiem, że spóźniłem się z odpowiedzią na to pytanie, ale w swoim problemie możesz zajrzeć do pakietu „joystick”. Zaprojektowałem go do wykreślania strumienia danych z portu szeregowego, ale działa dla każdego strumienia. Umożliwia także interaktywne rejestrowanie tekstu lub kreślenie obrazu (oprócz kreślenia wykresów). Nie musisz robić własnych pętli w osobnym wątku, pakiet dba o to, po prostu podaj żądaną częstotliwość aktualizacji. Ponadto terminal pozostaje dostępny do monitorowania poleceń podczas kreślenia. Zobacz http://www.github.com/ceyzeriat/joystick/ lub https://pypi.python.org/pypi/joystick (użyj pip install joystick, aby zainstalować)

Wystarczy zamienić np.random.random () na Twój rzeczywisty punkt danych odczytany z portu szeregowego w poniższym kodzie:

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
źródło