Mise à jour dynamique du tracé dans matplotlib

114

Je crée une application en Python qui collecte des données à partir d'un port série et trace un graphique des données collectées par rapport à l'heure d'arrivée. L'heure d'arrivée des données est incertaine. Je veux que le tracé soit mis à jour lorsque les données sont reçues. J'ai cherché comment faire cela et j'ai trouvé deux méthodes:

  1. Effacez le tracé et redessinez le tracé avec tous les points à nouveau.
  2. Animez le tracé en le modifiant après un intervalle particulier.

Je ne préfère pas le premier car le programme s'exécute et recueille des données pendant une longue période (un jour par exemple), et redessiner l'intrigue sera assez lent. Le second n'est pas non plus préférable car l'heure d'arrivée des données est incertaine et je souhaite que le graphique ne se mette à jour que lorsque les données sont reçues.

Existe-t-il un moyen de mettre à jour le tracé simplement en y ajoutant plus de points uniquement lorsque les données sont reçues?

Shadman Anwer
la source
2
Possible duplication du traçage en temps réel en boucle while avec matplotlib
Trevor Boyd Smith

Réponses:

138

Existe-t-il un moyen de mettre à jour l'intrigue simplement en y ajoutant plus de points ...

Il existe plusieurs façons d'animer des données dans matplotlib, selon la version dont vous disposez. Avez-vous vu les exemples de livres de recettes matplotlib ? Consultez également les exemples d'animation les plus modernes dans la documentation matplotlib. Enfin, l' 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 fondamentalement la datapropriété de l'objet dessiné, donc ne nécessite pas d'effacer l'écran ou la figure. La datapropriété peut simplement être étendue, de sorte que vous pouvez conserver les points précédents et continuer à ajouter à votre ligne (ou image ou tout ce que vous dessinez).

Étant donné que vous dites que l'heure d'arrivée de vos données est incertaine, votre meilleur pari 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, il vous suffit d'appeler update_line.

Chris
la source
Finalement! J'ai cherché une réponse à ce +1 :) Comment pouvons-nous redimensionner l'intrigue automatiquement. ax.set_autoscale_on (True) ne semble pas fonctionner.
Edward Newell
13
J'ai trouvé la réponse: appelez ax.relim () puis ax.autoscale_view () après la mise à jour des données mais avant d'appeler plt.draw ()
Edward Newell
Le lien vers le livre de recettes Matplotlib ( scipy.org/Cookbook/Matplotlib/Animations ) semble être rompu (j'obtiens une erreur "Interdit")
David Doria
21
Puisqu'il n'y a pas d'appel à show (), le tracé n'apparaît jamais à l'écran. Si j'appelle show (), il bloque et n'effectue pas les mises à jour. Est-ce que je manque quelque chose? gist.github.com/daviddoria/027b5c158b6f200527a4
David Doria
2
lien vers une réponse autonome similaire mais différente avec un code que vous pouvez exécuter (cette réponse a la bonne idée générale mais l'exemple de code ne peut pas être exécuté)
Trevor Boyd Smith
44

Pour ce faire sans FuncAnimation (par exemple, vous voulez exécuter d'autres parties du code pendant la production du tracé ou vous voulez mettre à jour plusieurs tracés en même temps), l'appel drawseul ne produit pas le tracé (du moins avec le qt backend).

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()
Zah
la source
Oui! Enfin une solution qui fonctionne avec Spyder! Ce qui me manquait, c'était gcf (). Canvas.flush_events () après la commande draw () -.
np8
Sur la base de cet excellent exemple, j'ai écrit un petit module Python permettant le traçage répétitif: github.com/lorenzschmid/dynplot
lorenzli
1
Un bel exemple!
vvy le
Claire, concise, polyvalente, flexible: telle devrait être la réponse acceptée.
pfabri le
Pour l'utiliser dans un Jupyter Notebook , vous devez ajouter la %matplotlib notebookcommande magic après votre instruction d'importation matplotlib.
pfabri le
3

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

Je sais que je suis en retard pour répondre à cette question, mais pour votre problème, vous pouvez consulter 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 de texte interactive ou le traçage d'images (en plus du tracé graphique). Pas besoin de faire vos propres boucles dans un thread séparé, le package s'en charge, 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 pip install joystick pour installer)

Remplacez simplement 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()
Guillaume S
la source