简体   繁体   中英

How can I dynamically update my matplotlib figure as the data file changes?

I have a python script that reads in a data file and displays one figure with four plots using the matplotlib library. The data file is being updated every few seconds since it is an output file for a different piece of software that is running concurrently. I would like the four plots in my matplotlib figure to refresh themselves using the updated data file every 20 seconds. The way I've implemented this is as follows:

import pylab as pl
import time

pl.ion()
fig = pl.figure()
while True:
    f = open('data.out', 'rb')
    #code to parse data and plot four charts
    ax = fig.add_subplot(2,2,1)
    #...
    ax = fig.add_subplot(2,2,4)
    #...
    pl.draw()
    time.sleep(20)

This works, but I lose functionality of the zoom and pan buttons which normally work if pl.show() is called. This is not optimal. However, if pl.show() is substituted for pl.draw(), the script no longer updates the plots. Is there a way to dynamically update a plot without completely losing the zoom/pan functionality?

Your code is a little too vague to know what is going on.

I can offer this: You should retain normal functionality if you create your subplots once, saving all the axes objects and then calling show().

Subsequent changes to those subplots could be done like this:

#inside while loop
for i in #subplotlist
    ax[i].clear()    #ax[i] is the axis object of the i'th subplot
    ax[i].plot(#plotstuff)
    ax[i].draw()

The toolbar for zooming and panning can be added by hand if you so desire.

As you are developping a sofware, I supposed you may have a multi-threaded approach. So in this case using an infinite while loop is a bad idea, like you are holding up your main thread.

In addition when it comes to GUI it's also a bad idea to interfere abruptly with GUI internal threads (wxPython for instance) and you should have an event driven design approach in order to not abruptly interrupt other threads (and that will cause the crash of your application).

The use of a timer will do the job.

A timer would do these actions in the following script :

1/ call a function to clear previous artist

2 / replot the data

3/ apply changes to canvas

4/ create another identical timer in the following design way : a timer who calls another identical timer after doing its job

Like I do not have access to your datas, I created a random data provider for the illustration. The defined variable delay_repeat allows you to program in seconds the refresh.

import pylab as pl
import random
from threading import Timer

def dataprovider():
    return [random.randint(0, 8) for i in range(8)]

def random_col():
    return ['blue', 'red', 'green', 'orange'][random.randint(0,3)]

# .... #
fig = pl.figure()
axes = [fig.add_subplot(2,2,i) for i in range(1,5)]
paths = [ax.scatter(x=dataprovider(), y=dataprovider(), marker = '+', c=random_col()) for ax in axes]
# .... #

def clear_arts(paths, all_arts=-1):
    if all_arts < 0:
        all_arts = len(paths)
    for path in paths[:all_arts]:
        path.remove()   

def refresh_arts(paths, delay_repeat):
    # 1 - clear previous artists
    clear_arts(paths,all_arts=-1)
    # 2 - Get artists paths for cleaning
    paths = [ax.scatter(x=dataprovider(), y=dataprovider(), marker = '+', c=random_col()) for ax in axes]
    # 3 - Apply changes
    fig.canvas.draw_idle()    
    # 4 - Create another timer
    Timer(delay_repeat, refresh_arts, (paths, delay_repeat)).start()

# 4- Create a timer that will run function with arguments args and keyword arguments kwargs, 
# after interval seconds have passed.
delay_repeat = 2
Timer(delay_repeat, refresh_arts, (paths, delay_repeat)).start()

# print("process continues here")

pl.show()

You can do it like this. It accept x,y as list and output a scatter plot plus a linear trend on the same plot.

from IPython.display import clear_output
from matplotlib import pyplot as plt
%matplotlib inline
    
def live_plot(x, y, figsize=(7,5), title=''):
    clear_output(wait=True)
    plt.figure(figsize=figsize)
    plt.xlim(0, training_steps)
    plt.ylim(0, 100)
    x= [float(i) for i in x]
    y= [float(i) for i in y]
    
    if len(x) > 1:
        plt.scatter(x,y, label='axis y', color='k') 
        m, b = np.polyfit(x, y, 1)
        plt.plot(x, [x * m for x in x] + b)

    plt.title(title)
    plt.grid(True)
    plt.xlabel('axis x')
    plt.ylabel('axis y')
    plt.show();

you just need to call live_plot(x, y) inside a loop. here's how it looks: 在此处输入图片说明

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM