Como atualizar um gráfico no matplotlib?

156

Estou tendo problemas para redesenhar a figura aqui. Permito que o usuário especifique as unidades na escala de tempo (eixo x) e depois recalculo e chamo essa função plots(). Quero que a trama simplesmente atualize, não acrescente outra trama à 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)
o apelido
fonte
1
Possível duplicado de tempo real plotagem em loop while com matplotlib
Trevor Boyd Smith

Respostas:

178

Você tem essencialmente duas opções:

  1. Faça exatamente o que está fazendo no momento, mas ligue graph1.clear()e graph2.clear()antes de substituir os dados. Essa é a opção mais lenta, porém mais simples e mais robusta.

  2. Em vez de substituir novamente, você pode apenas atualizar os dados dos objetos de plotagem. Você precisará fazer algumas alterações no seu código, mas isso deve ser muito, muito mais rápido do que substituir sempre as coisas. No entanto, a forma dos dados que você está plotando não pode mudar e, se o intervalo dos dados estiver mudando, você precisará redefinir manualmente os limites dos eixos xey.

Para dar um exemplo da segunda opção:

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
fonte
Eu tentei testar "1". e o resultado foi que, depois que eu reploquei os dados, outro conjunto de gráficos foi desenhado na minha GUI, então agora eu tinha quatro gráficos após o recálculo, como antes.
arquivo é o seguinte
@thenickname - Para onde exatamente você está ligando no seu código clear? Você deve ser chamado graph1.clear(); graph2.clear()dentro de seu forloop, apenas antes de chamar graph1.plot(...), graph2.plot(...)etc ...
Joe Kington
Esse loop for cria chamadas graphx.plot (...) N vezes e colocar as instruções claras nele apenas representa a última. Na verdade, eu peguei o código da tela e o coloquei no loop principal do programa junto com o código da figura e agora tenho minha função sendo chamada por um botão. Por alguma razão, se eu apenas chamar a função, os gráficos são atualizados, mas se eu pressionar o botão, os gráficos não. É um comportamento bastante interessante. Eu acho que deve ser um bug no Tkinter.
precisa
Apenas um palpite ... Você está perdendo uma chamada fig.canvas.draw()ou plt.draw()depois de plotar cada quadro? (Ou seja, você deve ter uma sequência de clear, plot, drawcada vez que você quiser mostrar um frame) Eu estou supondo, mas acho que poderia causar exatamente o comportamento que você está descrevendo ... Boa sorte, de qualquer forma!
quer
3
É 2k14 e eu tropecei para conseguir algo assim ... funciona como esperado, mas a janela de plotagem está girando "não está respondendo" .. alguma sugestão?
DevC
39

Você também pode fazer o seguinte: Isso desenhará uma matriz aleatória de 10x1 no gráfico para 50 ciclos do loop 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
fonte
Isso não parece gerar um gráfico. Estou esquecendo de algo? Eu também tenho %matplotlib inlineno caderno Jupyter.
MasayoMusic 5/09/19
haha, funcionou para mim quando removi o plt.clf(). Oh matplotlib, você Rascal :)
AndyP
15

Isso funcionou para mim. Chama repetidamente uma função que atualiza o gráfico sempre.

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

"diversão" é uma função que retorna um número inteiro. O FuncAnimation chamará repetidamente "update", fará isso vezes "xmax".

Vituel
fonte
Você poderia dar um exemplo de como você chama essa função (especialmente como você passa uma função em uma chamada de função) e como a função fun () se parece?
bjornasm
1
Certo. "fun ()" é qualquer função que retorna um número inteiro. Você pode passar a função como argumento para outro como este: "plot_cont (my_function, 123)". Lá está você me chamando plot_cont na linha 86: github.com/vitobasso/audio-ml/blob/…
Vituel
1
Observe que "a =" é necessário ou FuncAnimation será coletado como lixo e o código não funcionará!
Kiuhnm #
8

Caso alguém se depare com este artigo procurando o que eu estava procurando, encontrei exemplos em

Como visualizar dados 2D escalares com o Matplotlib?

e

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

depois os modificou para usar imshow com uma pilha de quadros de entrada, em vez de gerar e usar contornos em tempo real.


Começando com uma matriz 3D de imagens de forma (nBins, nBins, nBins), chamadas 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)

Também achei uma maneira muito mais simples de executar todo esse processo, embora menos 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

Note que ambos parecem funcionar ipython --pylab=tk, também conhecido comobackend = TkAgg

Obrigado pela ajuda com tudo.

CavemanPhD
fonte
6

Lancei um pacote chamado python-drawnow que fornece funcionalidade para permitir a atualização de uma figura, normalmente chamada dentro de um loop for, semelhante ao Matlab drawnow.

Um exemplo 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 pacote funciona com qualquer figura do matplotlib e fornece opções para aguardar após cada atualização da figura ou soltar no depurador.

Scott
fonte
2
Como é robusto e instável ao mesmo tempo?
BlueMoon93
2
Eu quis dizer robusto como em "trabalha com qualquer figura do matplotlib" e instável como em "projeto de fim de semana". Eu atualizei minha resposta
Scott
3

Todas as alternativas acima podem ser verdadeiras, no entanto, para mim, a "atualização on-line" das figuras funciona apenas com alguns back-ends, especificamente wx. Você pode tentar mudar para isso, por exemplo, iniciando o ipython / pylab by ipython --pylab=wx! Boa sorte!

jkeyser
fonte
1
Obrigado pela sua mensagem, nunca usei o modo interativo porque nunca funcionou com o back-end padrão que usei. É muito mais agradável usar o modo interativo do que interromper a execução sempre que você quiser ver um gráfico!
31413 Pierre Pierre
1
Nenhuma das outras respostas ajudou no meu caso. Estou usando pycharm e o problema estava com a plotagem e a interatividade do console. Eu precisava adicionar From pylab import * e então ion () no corpo do código para ativar o interativo. Agora funciona sem problemas para mim.
Shelbypereira 7/11
2

Isso funcionou para mim:

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
fonte