web-dev-qa-db-fra.com

Mise à jour dynamique du tracé dans matplotlib

Je réalise une application dans Python qui collecte des données à partir d'un port série et trace un graphique des données collectées en fonction du temps d'arrivée. L'heure d'arrivée des données est incertaine. Je veux que l'intrigue soit mise à jour lorsque les données sont reçues. J'ai cherché comment faire cela et trouvé deux méthodes:

  1. Effacer le tracé et re-dessiner le tracé avec tous les points à nouveau.
  2. Animer l'intrigue en le modifiant après un intervalle particulier.

Je ne préfère pas le premier car le programme exécute et collecte des données sur une longue période (un jour par exemple), et le tracé de l’intrigue sera plutôt lent. La seconde n'est également pas préférable car l'heure d'arrivée des données est incertaine et je souhaite que le graphique ne soit mis à jour que lorsque les données sont reçues.

Existe-t-il un moyen de mettre à jour l'intrigue en ajoutant simplement plus de points à la réception des données?

97
Shadman Anwer

Existe-t-il un moyen de mettre à jour l'intrigue en y ajoutant simplement un point [s] ...

Il existe différentes manières d’animer des données dans matplotlib, selon la version dont vous disposez. Avez-vous vu les exemples livre de recettes matplotlib ? Découvrez également le plus moderne exemples d'animation dans la documentation de matplotlib. Enfin, API d'animation définit une fonction FuncAnimation qui anime une fonction dans le temps. Cette fonction pourrait simplement être la fonction que vous utilisez pour acquérir vos données.

Chaque méthode définit en gros la propriété data de l'objet en cours de dessin, il n'est donc pas nécessaire d'effacer l'écran ou la figure. La propriété data peut simplement être étendue pour que vous puissiez conserver les points précédents et continuer à ajouter des éléments à votre ligne (ou à votre image ou à ce que vous dessinez).

Étant donné que vous dites que l'heure d'arrivée des données est incertaine, votre meilleur choix est probablement de faire quelque chose comme:

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

Ensuite, lorsque vous recevez des données du port série, appelez simplement update_line.

116
Chris

Afin de faire cela sans FuncAnimation (par exemple, vous voulez exécuter d'autres parties du code pendant que le tracé est produit ou vous voulez mettre à jour plusieurs parcelles simultanément), l'appel de draw seule ne produit pas la parcelle (au moins avec le backend qt).

Ce qui suit fonctionne pour moi:

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

Je sais que je suis en retard pour répondre à cette question, mais pour votre problème, vous pourriez vous pencher sur le package "joystick". Je l'ai conçu pour tracer un flux de données à partir du port série, mais cela fonctionne pour n'importe quel flux. Il permet également la journalisation interactive du texte ou le traçage des images (en plus du traçage des graphes). Pas besoin de faire vos propres boucles dans un thread séparé, le paquet s'en occupe, donnez simplement la fréquence de mise à jour que vous souhaitez. De plus, le terminal reste disponible pour surveiller les commandes pendant le traçage. Voir http://www.github.com/ceyzeriat/joystick/ ou https://pypi.python.org/pypi/joystick (utilisez le joystick d'installation de pip pour installer )

Il suffit de remplacer np.random.random () par votre point de données réel lu depuis le port série dans le code ci-dessous:

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

Voici un moyen qui permet de supprimer des points après un certain nombre de points tracés:

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