Come aggiornare un grafico in matplotlib?


156

Ho problemi con il ridisegno della figura qui. Autorizzo l'utente a specificare le unità nella scala temporale (asse x) e quindi ricalcolo e chiamo questa funzione plots(). Voglio semplicemente aggiornare la trama, non aggiungere un'altra trama alla figura.

def plots():
    global vlgaBuffSorted
    cntr()

    result = collections.defaultdict(list)
    for d in vlgaBuffSorted:
        result[d['event']].append(d)

    result_list = result.values()

    f = Figure()
    graph1 = f.add_subplot(211)
    graph2 = f.add_subplot(212,sharex=graph1)

    for item in result_list:
        tL = []
        vgsL = []
        vdsL = []
        isubL = []
        for dict in item:
            tL.append(dict['time'])
            vgsL.append(dict['vgs'])
            vdsL.append(dict['vds'])
            isubL.append(dict['isub'])
        graph1.plot(tL,vdsL,'bo',label='a')
        graph1.plot(tL,vgsL,'rp',label='b')
        graph2.plot(tL,isubL,'b-',label='c')

    plotCanvas = FigureCanvasTkAgg(f, pltFrame)
    toolbar = NavigationToolbar2TkAgg(plotCanvas, pltFrame)
    toolbar.pack(side=BOTTOM)
    plotCanvas.get_tk_widget().pack(side=TOP)

Risposte:


178

Hai essenzialmente due opzioni:

  1. Fai esattamente quello che stai facendo, ma chiama graph1.clear()e graph2.clear()prima di sostituire i dati. Questa è l'opzione più lenta, ma più semplice e robusta.

  2. Invece di sostituire, puoi semplicemente aggiornare i dati degli oggetti della trama. Dovrai apportare alcune modifiche al tuo codice, ma questo dovrebbe essere molto, molto più veloce della sostituzione delle cose ogni volta. Tuttavia, la forma dei dati che stai pianificando non può cambiare e se l'intervallo dei tuoi dati sta cambiando, dovrai reimpostare manualmente i limiti degli assi xey.

Per dare un esempio della seconda opzione:

import matplotlib.pyplot as plt
import numpy as np

x = np.linspace(0, 6*np.pi, 100)
y = np.sin(x)

# You probably won't need this if you're embedding things in a tkinter plot...
plt.ion()

fig = plt.figure()
ax = fig.add_subplot(111)
line1, = ax.plot(x, y, 'r-') # Returns a tuple of line objects, thus the comma

for phase in np.linspace(0, 10*np.pi, 500):
    line1.set_ydata(np.sin(x + phase))
    fig.canvas.draw()
    fig.canvas.flush_events()

Ho provato a provare "1" e il risultato è stato, dopo aver sostituito i dati, un altro set di grafici è stato disegnato nella mia GUI, quindi ora ho avuto 4 grafici dopo il ricalcolo, proprio come prima.
thenickname

@thenickname - Da dove chiami esattamente nel tuo codice clear? Dovresti chiamare graph1.clear(); graph2.clear()nel tuo forloop, appena prima di chiamare graph1.plot(...), graph2.plot(...)ecc ...
Joe Kington

Quello per il loop crea le chiamate graphx.plot (...) N volte e inserendo le chiare istruzioni traccia solo l'ultima. Ho effettivamente tirato fuori il codice di tela e l'ho inserito nel ciclo principale del programma insieme al codice di figura e ora ho la mia funzione richiamata da un pulsante. Per qualche motivo, se chiamo semplicemente la funzione, i grafici vengono aggiornati, ma se premo il pulsante i grafici non lo fanno. È un comportamento piuttosto interessante. Penso che debba essere un bug in Tkinter.
thenickname

Solo un sospetto ... Ti stai perdendo una chiamata fig.canvas.draw()oplt.draw() dopo aver tracciato ogni frame? (Vale a dire si dovrebbe avere una sequenza di clear, plot, drawogni volta che si desidera visualizzare un fotogramma) sto cercando di indovinare, ma penso che sarebbe causare esattamente il comportamento che stai descrivendo ... Buona fortuna, in ogni caso!
Joe Kington,

3
È 2k14 e mi sono imbattuto per ottenere qualcosa del genere ... funziona come previsto ma la finestra del diagramma sta girando "non risponde" .. qualche suggerimento ??
DevC,

39

Puoi anche fare quanto segue: In questo modo verranno disegnati dati di matrice casuali 10x1 sul grafico per 50 cicli del ciclo for.

import matplotlib.pyplot as plt
import numpy as np

plt.ion()
for i in range(50):
    y = np.random.random([10,1])
    plt.plot(y)
    plt.draw()
    plt.pause(0.0001)
    plt.clf()

Questo non sembra produrre un grafico. Mi sto perdendo qualcosa? Ho anche %matplotlib inlinenel quaderno Jupyter.
MasayoMusic,

haha, ha funzionato per me quando ho rimosso il plt.clf(). Oh matplotlib, mascalzone :)
AndyP il

15

Questo ha funzionato per me. Richiama ripetutamente una funzione che aggiorna il grafico ogni volta.

import matplotlib.pyplot as plt
import matplotlib.animation as anim

def plot_cont(fun, xmax):
    y = []
    fig = plt.figure()
    ax = fig.add_subplot(1,1,1)

    def update(i):
        yi = fun()
        y.append(yi)
        x = range(len(y))
        ax.clear()
        ax.plot(x, y)
        print i, ': ', yi

    a = anim.FuncAnimation(fig, update, frames=xmax, repeat=False)
    plt.show()

"fun" è una funzione che restituisce un numero intero. FuncAnimation chiamerà ripetutamente "update", lo farà "xmax" volte.


Potresti dare un esempio di come chiami questa funzione (specialmente come passi una funzione in una chiamata di funzione) e come appare la funzione fun ()?
bjornasm,

1
Sicuro. "fun ()" è qualsiasi funzione che restituisce un numero intero. È possibile passare la funzione come argomento a un altro come questo: "plot_cont (my_function, 123)". Lì mi hai fatto chiamare plot_cont alla linea 86: github.com/vitobasso/audio-ml/blob/…
Vituel

1
Nota che "a =" è necessario o FuncAnimation verrà raccolta in modo inutile e il codice non funzionerà!
Kiuhnm,

8

Nel caso in cui qualcuno trovi questo articolo cercando quello che stavo cercando, ho trovato degli esempi

Come visualizzare dati 2D scalari con Matplotlib?

e

http://mri.brechmos.org/2009/07/automatically-update-a-figure-in-a-loop (su web.archive.org)

poi li ha modificati per usare imshow con una pila di input di frame, invece di generare e usare contorni al volo.


A partire da una matrice 3D di immagini di forma (nBins, nBins, nBins), chiamate frames.

def animate_frames(frames):
    nBins   = frames.shape[0]
    frame   = frames[0]
    tempCS1 = plt.imshow(frame, cmap=plt.cm.gray)
    for k in range(nBins):
        frame   = frames[k]
        tempCS1 = plt.imshow(frame, cmap=plt.cm.gray)
        del tempCS1
        fig.canvas.draw()
        #time.sleep(1e-2) #unnecessary, but useful
        fig.clf()

fig = plt.figure()
ax  = fig.add_subplot(111)

win = fig.canvas.manager.window
fig.canvas.manager.window.after(100, animate_frames, frames)

Ho anche trovato un modo molto più semplice di percorrere l'intero processo, sebbene meno robusto:

fig = plt.figure()

for k in range(nBins):
    plt.clf()
    plt.imshow(frames[k],cmap=plt.cm.gray)
    fig.canvas.draw()
    time.sleep(1e-6) #unnecessary, but useful

Nota che entrambi sembrano funzionare solo con ipython --pylab=tk akabackend = TkAgg

Grazie per l'aiuto con tutto.


6

Ho rilasciato un pacchetto chiamato python-drawnow che fornisce funzionalità per consentire l'aggiornamento di una figura, in genere chiamato all'interno di un ciclo for, simile a quello di Matlabdrawnow .

Un esempio di utilizzo:

from pylab import figure, plot, ion, linspace, arange, sin, pi
def draw_fig():
    # can be arbitrarily complex; just to draw a figure
    #figure() # don't call!
    plot(t, x)
    #show() # don't call!

N = 1e3
figure() # call here instead!
ion()    # enable interactivity
t = linspace(0, 2*pi, num=N)
for i in arange(100):
    x = sin(2 * pi * i**2 * t / 100.0)
    drawnow(draw_fig)

Questo pacchetto funziona con qualsiasi figura matplotlib e fornisce opzioni per attendere dopo ogni aggiornamento di figura o rilasciarlo nel debugger.


2
Come è robusto e instabile allo stesso tempo?
BlueMoon93,

2
Intendevo robusto come in "funziona con qualsiasi figura matplotlib" e instabile come in "progetto weekend". Ho aggiornato la mia risposta
Scott

3

Tutto quanto sopra potrebbe essere vero, tuttavia per me "l'aggiornamento online" delle figure funziona solo con alcuni backend, in particolare wx. Potresti semplicemente provare a cambiare in questo, ad esempio avviando ipython / pylab con ipython --pylab=wx! In bocca al lupo!


1
Grazie per il tuo messaggio, non ho mai usato la modalità interattiva perché non ha mai funzionato con il backend predefinito che ho usato. È molto più bello usare la modalità interattiva piuttosto che interrompere l'esecuzione ogni volta che vuoi vedere un grafico!
PierreE,

1
Nessuna delle altre risposte ha aiutato nel mio caso. Sto usando Pycharm e il problema era con la trama e l'interattività della console. Ho dovuto aggiungere From import pylab * e quindi ion () nel corpo del codice per attivare interattivo. Funziona senza problemi ora per me.
shelbypereira,

2

Questo ha funzionato per me:

from matplotlib import pyplot as plt
from IPython.display import clear_output
import numpy as np
for i in range(50):
    clear_output(wait=True)
    y = np.random.random([10,1])
    plt.plot(y)
    plt.show()
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.