Plot in matplotlib dynamisch aktualisieren


114

Ich mache eine Anwendung in Python, die Daten von einer seriellen Schnittstelle sammelt und ein Diagramm der gesammelten Daten gegen die Ankunftszeit zeichnet. Die Ankunftszeit für die Daten ist ungewiss. Ich möchte, dass der Plot aktualisiert wird, wenn Daten empfangen werden. Ich suchte danach und fand zwei Methoden:

  1. Löschen Sie den Plot und zeichnen Sie den Plot mit allen Punkten erneut.
  2. Animieren Sie das Diagramm, indem Sie es nach einem bestimmten Intervall ändern.

Ich bevorzuge nicht die erste, da das Programm läuft und Daten für eine lange Zeit sammelt (zum Beispiel einen Tag), und das Neuzeichnen des Plots wird ziemlich langsam sein. Der zweite ist ebenfalls nicht vorzuziehen, da der Zeitpunkt des Eintreffens der Daten ungewiss ist und ich möchte, dass der Plot nur aktualisiert wird, wenn die Daten empfangen werden.

Gibt es eine Möglichkeit, das Diagramm zu aktualisieren, indem ich nur dann weitere Punkte hinzufüge, wenn die Daten empfangen werden?


Antworten:


138

Gibt es eine Möglichkeit, die Handlung zu aktualisieren, indem ich weitere Punkte hinzufüge ...

Abhängig von Ihrer Version gibt es verschiedene Möglichkeiten, Daten in matplotlib zu animieren. Haben Sie die Beispiele für das Matplotlib-Kochbuch gesehen ? Schauen Sie sich auch die moderneren Animationsbeispiele in der matplotlib-Dokumentation an. Schließlich definiert die Animations-API eine Funktion FuncAnimation, die eine Funktion rechtzeitig animiert. Diese Funktion kann nur die Funktion sein, mit der Sie Ihre Daten erfassen.

Jede Methode legt im Grunde die dataEigenschaft des zu zeichnenden Objekts fest, sodass weder der Bildschirm noch die Abbildung gelöscht werden müssen. Die dataEigenschaft kann einfach erweitert werden, sodass Sie die vorherigen Punkte beibehalten und Ihre Linie (oder Ihr Bild oder was auch immer Sie zeichnen) weiter ergänzen können.

Angesichts der Tatsache, dass Sie sagen, dass Ihre Datenankunftszeit ungewiss ist, besteht Ihre beste Wahl wahrscheinlich darin, Folgendes zu tun:

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

Wenn Sie dann Daten von der seriellen Schnittstelle empfangen, rufen Sie einfach an update_line.


Schließlich! Ich habe nach einer Antwort auf diese +1 gesucht :) Wie können wir die Handlung automatisch neu skalieren? ax.set_autoscale_on (True) scheint nicht zu funktionieren.
Edward Newell

13
Die Antwort gefunden: Rufen Sie ax.relim () und dann ax.autoscale_view () auf, nachdem Sie die Daten aktualisiert haben, aber bevor Sie plt.draw () aufrufen
Edward Newell

Der Link zum Matplotlib-Kochbuch ( scipy.org/Cookbook/Matplotlib/Animations ) scheint unterbrochen zu sein (ich erhalte den Fehler "Verboten")
David Doria

21
Da show () nicht aufgerufen wird, wird das Diagramm nie auf dem Bildschirm angezeigt. Wenn ich show () aufrufe, werden die Updates blockiert und nicht ausgeführt. Vermisse ich etwas gist.github.com/daviddoria/027b5c158b6f200527a4
David Doria

2
Link zu einer ähnlichen, aber anderen in sich geschlossenen Antwort mit Code, den Sie ausführen können (diese Antwort hat die richtige allgemeine Idee, aber der Beispielcode kann nicht ausgeführt werden)
Trevor Boyd Smith

44

Um dies ohne FuncAnimation zu tun (z. B. wenn Sie andere Teile des Codes ausführen möchten, während der Plot erstellt wird, oder wenn Sie mehrere Plots gleichzeitig aktualisieren möchten), rufen Sie auf draw der Plot allein durch nicht erzeugt (zumindest mit dem qt Backend).

Folgendes funktioniert für mich:

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

Ja! Endlich eine Lösung, die mit Spyder funktioniert! Was mir fehlte, war gcf (). Canvas.flush_events () nach dem Befehl draw () -.
np8

Basierend auf diesem großartigen Beispiel habe ich ein kleines Python-Modul geschrieben, das wiederholtes Plotten ermöglicht: github.com/lorenzschmid/dynplot
lorenzli

1
Ein schönes Beispiel!
Vvy

Klar, prägnant, vielseitig, flexibel: Dies sollte die akzeptierte Antwort sein.
Pfabri

Um dies in einem Jupyter-Notizbuch zu verwenden , müssen Sie den %matplotlib notebookBefehl magic nach Ihrer matplotlib-Importanweisung hinzufügen .
Pfabri

3

Hier ist eine Möglichkeit, Punkte nach einer bestimmten Anzahl gezeichneter Punkte zu entfernen:

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

Ich weiß, dass ich zu spät bin, um diese Frage zu beantworten, aber für Ihr Problem könnten Sie sich das "Joystick" -Paket ansehen. Ich habe es zum Plotten eines Datenstroms von der seriellen Schnittstelle entwickelt, aber es funktioniert für jeden Stream. Es ermöglicht auch die interaktive Textprotokollierung oder Bilddarstellung (zusätzlich zur grafischen Darstellung). Sie müssen keine eigenen Schleifen in einem separaten Thread erstellen, das Paket kümmert sich darum. Geben Sie einfach die gewünschte Aktualisierungshäufigkeit an. Außerdem bleibt das Terminal für die Überwachung von Befehlen während des Plots verfügbar. Siehe http://www.github.com/ceyzeriat/joystick/ oder https://pypi.python.org/pypi/joystick (verwenden Sie den Pip-Installations-Joystick zur Installation).

Ersetzen Sie einfach np.random.random () durch Ihren realen Datenpunkt, der von der seriellen Schnittstelle im folgenden Code gelesen wird:

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()
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.