Comment mettre à jour un tracé dans matplotlib?


156

Je rencontre des problèmes pour redessiner la figure ici. J'autorise l'utilisateur à spécifier les unités dans l'échelle de temps (axe des x), puis je recalcule et appelle cette fonction plots(). Je veux que l'intrigue se mette simplement à jour, et non en ajoute une autre à la figure.

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)

Réponses:


178

Vous avez essentiellement deux options:

  1. Faites exactement ce que vous faites actuellement, mais appelez graph1.clear()et graph2.clear()avant de reconstituer les données. C'est l'option la plus lente, mais la plus simple et la plus robuste.

  2. Au lieu de replotter, vous pouvez simplement mettre à jour les données des objets de tracé. Vous devrez apporter des modifications à votre code, mais cela devrait être beaucoup, beaucoup plus rapide que de reconstituer les choses à chaque fois. Cependant, la forme des données que vous tracez ne peut pas changer, et si la plage de vos données change, vous devrez réinitialiser manuellement les limites des axes x et y.

Pour donner un exemple de la deuxième option:

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

J'ai essayé de tester "1". et le résultat était, après avoir reconstitué les données, un autre ensemble de tracés a été dessiné dans mon interface graphique, donc maintenant j'avais 4 tracés après recalcul, juste comme avant.
thenickname

@thenickname - Où appelez-vous exactement dans votre code clear? Vous devriez appeler graph1.clear(); graph2.clear()dans votre forboucle, juste avant d'appeler graph1.plot(...), graph2.plot(...)etc ...
Joe Kington

Cette boucle for crée des appels graphx.plot (...) N fois et y placer les instructions claires ne trace que la dernière. J'ai en fait sorti le code du canevas et l'ai mis dans la boucle principale du programme avec le code de la figure et ma fonction est maintenant appelée par un bouton. Pour une raison quelconque, si j'appelle simplement la fonction, les graphiques sont mis à jour, mais si j'appuie sur le bouton, les graphiques ne le sont pas. C'est un comportement assez intéressant. Je pense que cela doit être un bug dans Tkinter.
thenickname

Juste une intuition ... Vous manquez un appel fig.canvas.draw()ou plt.draw()après avoir tracé chaque image? (Vous devriez avoir une séquence de clear, plot, drawchaque fois que vous voulez montrer un cadre) Je devine, mais je pense que cela causerait exactement le comportement que vous décrivez ... Bonne chance, en tout cas!
Joe Kington

3
C'est 2k14 et j'ai trébuché pour réaliser quelque chose comme ça ... cela fonctionne comme prévu mais la fenêtre de traçage tourne "ne répond pas" .. des suggestions ??
DevC

39

Vous pouvez également faire comme suit: Cela dessinera une matrice aléatoire de données 10x1 sur le tracé pendant 50 cycles de la boucle 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()

Cela ne semble pas générer de graphique. Est-ce que je manque quelque chose? J'ai aussi %matplotlib inlinedans le cahier Jupyter.
MasayoMusic

haha, a travaillé pour moi lorsque j'ai supprimé le plt.clf(). Oh matplotlib, you
rascal

15

Cela a fonctionné pour moi. Appelle à plusieurs reprises une fonction mettant à jour le graphique à chaque fois.

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" est une fonction qui renvoie un entier. FuncAnimation appellera à plusieurs reprises "update", il le fera fois "xmax".


Pourriez-vous donner un exemple sur la façon dont vous appelez cette fonction (en particulier comment vous passez une fonction dans un appel de fonction) ainsi que sur l'apparence de la fonction fun ()?
bjornasm

1
Sûr. "fun ()" est n'importe quelle fonction qui renvoie un entier. Vous pouvez passer la fonction comme argument à un autre comme ceci: "plot_cont (my_function, 123)". Voilà, vous m'avez appelé plot_cont à la ligne 86: github.com/vitobasso/audio-ml/blob/…
Vituel

1
Notez que "a =" est nécessaire ou FuncAnimation sera ramassé et le code ne fonctionnera pas!
Kiuhnm

8

Au cas où quelqu'un trouverait cet article à la recherche de ce que je cherchais, j'ai trouvé des exemples sur

Comment visualiser des données 2D scalaires avec Matplotlib?

et

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

puis les a modifiés pour utiliser imshow avec une pile d'entrée de cadres, au lieu de générer et d'utiliser des contours à la volée.


En commençant par un tableau 3D d'images de forme (nBins, nBins, nBins), appelé 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)

J'ai également trouvé un moyen beaucoup plus simple de gérer tout ce processus, bien que moins robuste:

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

Notez que les deux semblent fonctionner uniquement avec ipython --pylab=tk, akabackend = TkAgg

Merci pour l'aide avec tout.


6

J'ai publié un package appelé python-drawnow qui fournit des fonctionnalités permettant de mettre à jour une figure, généralement appelée dans une boucle for, similaire à celle de Matlab drawnow.

Un exemple d'utilisation:

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)

Ce package fonctionne avec n'importe quelle figure matplotlib et fournit des options pour attendre après chaque mise à jour de la figure ou déposer dans le débogueur.


2
En quoi est-il à la fois robuste et instable?
BlueMoon93

2
Je voulais dire robuste comme dans "fonctionne avec n'importe quelle figure matplotlib" et instable comme dans "projet de week-end". J'ai mis à jour ma réponse
Scott

3

Tout ce qui précède est peut-être vrai, mais pour moi, la "mise à jour en ligne" des chiffres ne fonctionne qu'avec certains backends, en particulier wx. Vous pouvez simplement essayer de changer cela, par exemple en démarrant ipython / pylab par ipython --pylab=wx! Bonne chance!


1
Merci pour votre message, je n'ai jamais utilisé le mode interactif car il n'a jamais fonctionné avec le backend par défaut que j'ai utilisé. Il est bien plus agréable d'utiliser le mode interactif que d'arrêter l'exécution à chaque fois que vous voulez voir un graphique!
PierreE

1
Aucune des autres réponses n'a aidé dans mon cas. J'utilise pycharm et le problème était avec le traçage et l'interactivité de la console. J'avais besoin d'ajouter From pylab import * puis ion () dans le corps du code pour activer l'interactivité. Cela fonctionne bien maintenant pour moi.
shelbypereira

2

Cela a fonctionné pour moi:

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()
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.