Aggiornamento dinamico della trama in matplotlib


114

Sto realizzando un'applicazione in Python che raccoglie i dati da una porta seriale e traccia un grafico dei dati raccolti rispetto all'ora di arrivo. L'ora di arrivo dei dati è incerta. Desidero che il grafico venga aggiornato quando vengono ricevuti i dati. Ho cercato come farlo e ho trovato due metodi:

  1. Cancella la trama e ridisegna di nuovo la trama con tutti i punti.
  2. Anima la trama modificandola dopo un determinato intervallo.

Non preferisco il primo in quanto il programma viene eseguito e raccoglie dati per molto tempo (un giorno per esempio), e ridisegnare la trama sarà piuttosto lento. Anche il secondo non è preferibile in quanto il tempo di arrivo dei dati è incerto e voglio che la trama si aggiorni solo quando i dati vengono ricevuti.

C'è un modo in cui posso aggiornare il grafico semplicemente aggiungendo più punti ad esso solo quando i dati vengono ricevuti?


Risposte:


138

C'è un modo in cui posso aggiornare la trama semplicemente aggiungendo più punti ad essa ...

Esistono diversi modi per animare i dati in matplotlib, a seconda della versione che hai. Hai visto gli esempi del libro di cucina matplotlib ? Inoltre, controlla gli esempi di animazione più moderni nella documentazione di matplotlib. Infine, l' API di animazione definisce una funzione FuncAnimation che anima una funzione nel tempo. Questa funzione potrebbe essere solo la funzione che usi per acquisire i tuoi dati.

Ogni metodo imposta fondamentalmente la dataproprietà dell'oggetto da disegnare, quindi non richiede la cancellazione dello schermo o della figura. La dataproprietà può essere semplicemente estesa, quindi puoi mantenere i punti precedenti e continuare ad aggiungere alla tua linea (o immagine o qualunque cosa tu stia disegnando).

Dato che dici che l'orario di arrivo dei tuoi dati è incerto, la soluzione migliore è probabilmente fare qualcosa come:

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

Quindi quando ricevi i dati dalla porta seriale chiama update_line.


Finalmente! Ho cercato una risposta a questo +1 :) Come facciamo a ridimensionare automaticamente la trama. ax.set_autoscale_on (True) non sembra funzionare.
Edward Newell

13
Trovato la risposta: chiama ax.relim () quindi ax.autoscale_view () dopo aver aggiornato i dati ma prima di chiamare plt.draw ()
Edward Newell

Il collegamento al libro di cucina Matplotlib ( scipy.org/Cookbook/Matplotlib/Animations ) sembra essere interrotto (ricevo un errore "Proibito")
David Doria

21
Poiché non è presente alcuna chiamata a show (), il grafico non viene mai visualizzato sullo schermo. Se chiamo show (), blocca e non esegue gli aggiornamenti. Mi sto perdendo qualcosa? gist.github.com/daviddoria/027b5c158b6f200527a4
David Doria

2
collegamento a una risposta autonoma simile ma diversa con codice che è possibile eseguire (questa risposta ha la giusta idea generale ma il codice di esempio non può essere eseguito)
Trevor Boyd Smith

44

Per fare ciò senza FuncAnimation (ad esempio, si desidera eseguire altre parti del codice mentre viene prodotto il grafico o si desidera aggiornare più grafici contemporaneamente), chiamare drawda solo non produce il grafico (almeno con il qt backend).

Il seguente funziona per me:

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

Sì! Finalmente una soluzione che funziona con Spyder! La cosa che mi mancava era gcf (). Canvas.flush_events () dopo il comando draw () -.
np8

Sulla base di questo ottimo esempio ho scritto un piccolo modulo Python che consente il
tracciamento

1
Un bell'esempio!
vvy

Chiaro, conciso, versatile, flessibile: questa dovrebbe essere la risposta accettata.
pfabri

Per usarlo in un Jupyter Notebook , è necessario aggiungere il %matplotlib notebookmagic command dopo l'istruzione import matplotlib.
pfabri

3

Ecco un modo che consente di rimuovere punti dopo un certo numero di punti tracciati:

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

So di essere in ritardo per rispondere a questa domanda, ma per il tuo problema potresti esaminare il pacchetto "joystick". L'ho progettato per tracciare un flusso di dati dalla porta seriale, ma funziona per qualsiasi flusso. Consente inoltre la registrazione interattiva del testo o la stampa di immagini (oltre alla rappresentazione grafica). Non c'è bisogno di fare i propri loop in un thread separato, il pacchetto si prende cura di esso, basta dare la frequenza di aggiornamento che desideri. Inoltre il terminale rimane disponibile per il monitoraggio dei comandi durante la stampa. Vedi http://www.github.com/ceyzeriat/joystick/ o https://pypi.python.org/pypi/joystick (usa pip install joystick per installare)

Basta sostituire np.random.random () con il tuo punto dati reale letto dalla porta seriale nel codice seguente:

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()
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.