¿Cómo actualizar una trama en matplotlib?

156

Tengo problemas para volver a dibujar la figura aquí. Permito que el usuario especifique las unidades en la escala de tiempo (eje x) y luego recalculo y llamo a esta función plots(). Quiero que la trama simplemente se actualice, no agregue otra trama a la 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)
el sobrenombre
fuente
1
Posible duplicado del trazado en tiempo real en el bucle while con matplotlib
Trevor Boyd Smith

Respuestas:

178

Básicamente tienes dos opciones:

  1. Haz exactamente lo que estás haciendo actualmente, pero llama graph1.clear() y graph2.clear()antes de responder los datos. Esta es la opción más lenta, pero más simple y robusta.

  2. En lugar de responder, solo puede actualizar los datos de los objetos de la trama. Tendrá que hacer algunos cambios en su código, pero esto debería ser mucho, mucho más rápido que responder cada vez. Sin embargo, la forma de los datos que está trazando no puede cambiar, y si el rango de sus datos está cambiando, deberá restablecer manualmente los límites de los ejes xey.

Para dar un ejemplo de la segunda opción:

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()
Joe Kington
fuente
Traté de probar "1". y el resultado fue que, después de responder los datos, se dibujó otro conjunto de gráficos en mi GUI, por lo que ahora tenía 4 gráficos después del recálculo, al igual que antes.
thenickname el
@thenickname - ¿Dónde exactamente en tu código estás llamando clear? Debería llamar graph1.clear(); graph2.clear()dentro de su forbucle, justo antes de llamar graph1.plot(...), graph2.plot(...)etc ...
Joe Kington
Eso for loop crea calles graphx.plot (...) N veces y poner las declaraciones claras allí solo traza la última. De hecho, saqué el código del lienzo y lo puse en el bucle principal del programa junto con el código de la figura y ahora tengo mi función llamada por un botón. Por alguna razón, si solo llamo a la función, los gráficos se actualizan, pero si presiono el botón, los gráficos no. Es un comportamiento bastante interesante. Creo que debe ser un error en Tkinter.
thenickname
Solo una corazonada ... ¿Te estás perdiendo una llamada fig.canvas.draw()o plt.draw()después de trazar cada fotograma? (Es decir, usted debe tener una secuencia de clear, plot, drawcada vez que se desea mostrar un cuadro) supongo, pero creo que causaría exactamente el comportamiento que describes ... Buena suerte, de todos modos!
Joe Kington el
3
Es 2k14 y tropecé para lograr algo como esto ... funciona como se esperaba pero la ventana de trazado se está volviendo "no responde" ... ¿alguna sugerencia?
DevC
39

También puede hacer lo siguiente: Esto dibujará una matriz aleatoria de 10x1 en el gráfico durante 50 ciclos 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()
Arindam
fuente
Esto no parece generar un gráfico. ¿Me estoy perdiendo de algo? Tengo %matplotlib inlineen el cuaderno Jupyter también.
MasayoMusic
jaja, funcionó para mí cuando quité el plt.clf(). Oh matplotlib, bribón :)
AndyP
15

Esto funcionó para mí. Llama repetidamente a una función que actualiza el gráfico cada vez.

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

"diversión" es una función que devuelve un número entero. FuncAnimation repetidamente llamará "actualizar", lo hará "xmax" veces.

Vituel
fuente
¿Podría dar un ejemplo de cómo llama a esta función (especialmente cómo pasa una función en una llamada de función) y cómo se ve la función fun ()?
bjornasm
1
Por supuesto. "fun ()" es cualquier función que devuelve un entero. Puede pasar la función como argumento a otro como este: "plot_cont (my_function, 123)". Ahí me tienes llamando plot_cont en la línea 86: github.com/vitobasso/audio-ml/blob/…
Vituel
1
Tenga en cuenta que "a =" es necesario o FuncAnimation se recolectará basura y el código no funcionará.
Kiuhnm
8

En caso de que alguien encuentre este artículo buscando lo que estaba buscando, encontré ejemplos en

¿Cómo visualizar datos 2D escalares con Matplotlib?

y

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

luego los modificó para usar imshow con una pila de cuadros de entrada, en lugar de generar y usar contornos sobre la marcha.


Comenzando con una matriz 3D de imágenes de forma (nBins, nBins, nBins), llamadas 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)

También encontré una forma mucho más simple de realizar todo este proceso, aunque menos robusta:

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

Tenga en cuenta que ambos parecen funcionar solo con ipython --pylab=tk , también conocido comobackend = TkAgg

Gracias por la ayuda con todo.

CavemanPhD
fuente
6

He lanzado un paquete llamado python-drawnow que proporciona funcionalidad para permitir que una figura se actualice, normalmente llamada dentro de un bucle for, similar a la de Matlab drawnow.

Un ejemplo de uso:

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)

Este paquete funciona con cualquier figura matplotlib y proporciona opciones para esperar después de cada actualización de la figura o caer en el depurador.

Scott
fuente
2
¿Cómo es robusto e inestable al mismo tiempo?
BlueMoon93
2
Quise decir robusto como en "funciona con cualquier figura matplotlib" e inestable como en "proyecto de fin de semana". He actualizado mi respuesta
Scott, el
3

Todo lo anterior podría ser cierto, sin embargo, para mí, la "actualización en línea" de cifras solo funciona con algunos backends, específicamente wx. Puede intentar cambiar esto, por ejemplo, iniciando ipython / pylab por ipython --pylab=wx! ¡Buena suerte!

jkeyser
fuente
1
Gracias por su mensaje, nunca usé el modo interactivo porque nunca funcionó con el backend predeterminado que usé. ¡Es mucho mejor usar el modo interactivo que detener la ejecución cada vez que quiera ver un gráfico!
PierreE
1
Ninguna de las otras respuestas ayudó en mi caso. Estoy usando pycharm y el problema fue con el trazado y la interactividad de la consola. Necesitaba agregar From pylab import * y luego ion () en el cuerpo del código para activar el interactivo. Funciona sin problemas ahora para mí.
shelbypereira
2

Esto funcionó para mí:

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