Atualizando o gráfico dinamicamente em matplotlib

114

Estou fazendo um aplicativo em Python que coleta dados de uma porta serial e traça um gráfico dos dados coletados em relação ao tempo de chegada. A hora de chegada dos dados é incerta. Quero que o gráfico seja atualizado quando os dados forem recebidos. Pesquisei como fazer isso e encontrei dois métodos:

  1. Limpe o gráfico e redesenhe o gráfico com todos os pontos novamente.
  2. Anime o enredo alterando-o após um determinado intervalo.

Eu não prefiro o primeiro porque o programa roda e coleta dados por um longo tempo (um dia, por exemplo), e redesenhar o gráfico será bem lento. O segundo também não é preferível, pois o tempo de chegada dos dados é incerto e eu quero que o gráfico seja atualizado apenas quando os dados forem recebidos.

Existe uma maneira de atualizar o gráfico apenas adicionando mais pontos a ele apenas quando os dados forem recebidos?

Shadman Anwer
fonte
2
Possível duplicata de plotagem em tempo real em loop while com matplotlib
Trevor Boyd Smith

Respostas:

138

Existe uma maneira de atualizar o gráfico apenas adicionando mais pontos a ele ...

Existem várias maneiras de animar dados em matplotlib, dependendo da versão que você possui. Você viu o exemplos do livro de receitas matplotlib ? Além disso, verifique os exemplos de animação mais modernos na documentação do matplotlib. Finalmente, a API de animação define uma função FuncAnimation que anima uma função no tempo. Esta função pode ser apenas a função que você usa para adquirir seus dados.

Cada método basicamente define a datapropriedade do objeto que está sendo desenhado, portanto, não requer a limpeza da tela ou figura. A datapropriedade pode ser simplesmente estendida, para que você possa manter os pontos anteriores e apenas continuar adicionando à sua linha (ou imagem ou o que quer que esteja desenhando).

Considerando que você diz que seu horário de chegada de dados é incerto, sua melhor aposta é provavelmente apenas fazer algo como:

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

Então quando você receber dados da porta serial é só ligar update_line.

Chris
fonte
Finalmente! Estou procurando uma resposta para este +1 :) Como fazemos a redimensionamento do enredo automaticamente. ax.set_autoscale_on (True) não parece funcionar.
Edward Newell
13
Encontrou a resposta: chame ax.relim () e depois ax.autoscale_view () depois de atualizar os dados, mas antes de chamar plt.draw ()
Edward Newell
O link para o livro de receitas Matplotlib ( scipy.org/Cookbook/Matplotlib/Animations ) parece estar quebrado (recebo um erro "Proibido")
David Doria
21
Como não há chamada para show (), o gráfico nunca aparece na tela. Se eu chamar show (), ele bloqueia e não executa as atualizações. Estou esquecendo de algo? gist.github.com/daviddoria/027b5c158b6f200527a4
David Doria
2
link para uma resposta independente semelhante, mas diferente, com código que você pode executar (esta resposta tem a ideia geral certa, mas o código de exemplo não pode ser executado)
Trevor Boyd Smith
44

Para fazer isso sem FuncAnimation (por exemplo, você deseja executar outras partes do código enquanto o gráfico está sendo produzido ou deseja atualizar vários gráficos ao mesmo tempo), chamando draw sozinho não produz o gráfico (pelo menos com o backend qt).

O seguinte funciona para mim:

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()
Zah
fonte
Sim! Finalmente uma solução que funciona com o Spyder! O que estava faltando era gcf (). Canvas.flush_events () após o comando draw () -.
np8
Com base neste ótimo exemplo, escrevi um pequeno módulo Python que permite plotagem repetitiva: github.com/lorenzschmid/dynplot
lorenzli
1
Um lindo exemplo!
vvy
Clara, concisa, versátil, flexível: esta deve ser a resposta aceita.
pfabri
Para usar isso em um Notebook Jupyter , você deve adicionar o %matplotlib notebookcomando mágico após sua instrução de importação matplotlib.
pfabri
3

Esta é uma maneira que permite remover pontos após um certo número de pontos traçados:

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()
NDM
fonte
2

Sei que estou atrasado para responder a essa pergunta, mas para seu problema, você pode olhar para o pacote "joystick". Eu o projetei para traçar um fluxo de dados da porta serial, mas funciona para qualquer fluxo. Também permite registro de texto interativo ou plotagem de imagem (além de plotagem de gráfico). Não há necessidade de fazer seus próprios loops em uma thread separada, o pacote cuida disso, basta fornecer a frequência de atualização desejada. Além disso, o terminal permanece disponível para monitorar comandos durante a plotagem. Consulte http://www.github.com/ceyzeriat/joystick/ ou https://pypi.python.org/pypi/joystick (use o joystick de instalação do pip para instalar)

Basta substituir np.random.random () pelo seu ponto de dados real lido da porta serial no código abaixo:

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()
Guillaume S
fonte