Using events with matplotlib in a for loop

2020-03-31 04:59发布

I apologize since I have open another ticket before on a related topic. Thanks to the answers I got now I can be more specific. I have also received some solutions based on Tkinter, but I would like to solve my problems with events and loops.

The particular case I am dealing with is as follows: I have an array of arrays. I want matplotlib to plot the first element of it, allow me to press one key (with an associated event), and the program plots the second array, same behaviour, and so on.

As a quick example:

import matplotlib.pyplot as plt
import numpy as np

# Define the event
def ontype(event):
    if event.key == '1':
        print 'It is working'
        plt.clf()

# Create figure an connect the event to it
fig=plt.figure(figsize=(16,8))
plt.gcf().canvas.mpl_connect('key_press_event',ontype)

# Loop
for element in xrange(10):
    #This mimicks the "array of arrays" generating a random array in each loop
    vector = np.random.random(10)  
    plt.plot(vector)
    plt.show()

I would expect to get a first plot (the first time the loop runs), and that it is left open until I press 1. However, what I get is an figure with the ten vectors plotted, and when I press 1 the figure is cleared and it says "It is working" via terminal. I need the program to plot the first one, and move to the next element once a key has been pressed. Any hint on this? What am I doing wrong?

Thank you guys!

EDIT:

Please keep in mind that in principle, the structure of the program can not be varied, and the for loop is needed to compute different things before plotting anything. Hence, the program should go

def ontype(event):
     define event

Some stuff
elements = array of arrays
for element in elements:
    do more stuff
    plot element and "stay" in this plot untill any event key is pressed. And then, go to the next element in elements and do the same

EDIT 2:

I think I didn't explained myself properly and the kind of data might have been missunderstood. In my case, I am reading a huge table of data, and each line is a different source. Whay I am trying to plot is the information of the columns. I am a physicist, so I don't have much knowledge about stylish programming or anything. The problem is...if there is no way to do this with a for loop, could anyone explain me how to do this kind of work without it?

2条回答
家丑人穷心不美
2楼-- · 2020-03-31 05:33

This next block is what does what you want with the for loop.

def ugly_math():
    print 'you will hit this once'
    for j in range(10):
        print 'loop ', j
        # insert math here
        yield  np.random.random(10) * j

Your for loop goes into the function ugly_math, and what you want plotted is what goes after yield. see What does the "yield" keyword do in Python? . In short, yield turns a function with a loop into a generator factory.

fun = ugly_math()

is then a generator. When you call fun.next() it will run the function ugly_math until it hits the yield. It will then return the value yielded (in this example, np.random.random). The next time you call fun.next() it will pick up where it left off in the loop and run until it hits yield again. Hence it does exactly what you want.

Then borrowing heavily from Holger:

fun = ugly_math()
cid_dict = {}
# Define the event
def ontype(event):
    if event.key == '1':
        print 'It is working'
        try:
            vector = fun.next()
            plt.plot(vector)
            fig.canvas.draw()
        except StopIteration:
            plt.gcf().canvas.mpl_disconnect(cid_dict['cid'])
            del cid_dict['cid']

# Create figure an connect the event to it
fig=plt.figure(figsize=(16,8))
cid_dict['cid'] = plt.gcf().canvas.mpl_connect('key_press_event',ontype)

vector = np.random.random(10)  
plt.plot(vector)
plt.show()

The cid_dict is there so that we can remove the call-back after we have exhausted the generator.

We can wrap this all up into a class as such

class push_to_advance(object):
    def __init__(self):
        self.fig = plt.figure()
        self.ax = self.fig.gca()
        self.bound_keys = []
        self.bound_cid = {}

    def add_step_through(self, gen, key):
        key = key[0] # make a single char
        if key in self.bound_keys:
            raise RuntimeError("key %s already bound"%key)
        first_data = gen.next()
        self.ax.plot(first_data)
        self.fig.canvas.draw()
        self.bound_keys.append(key)
        def ontype(event):
            if event.key == key:
                try:
                    self.ax.plot(gen.next())
                    self.fig.canvas.draw()
                except StopIteration:
                    self.fig.canvas.mpl_disconnect(self.bound_cid[key])
                    del self.bound_cid[key]
                    self.bound_keys.remove(key)

        self.bound_cid[key] = self.fig.canvas.mpl_connect('key_press_event', ontype)

This is used as such:

 pta = push_to_advance()
 gen = ugly_math()
 pta.add_step_through(gen,'a')

Any iterable will work with a bit of finessing:

 test_array = np.arange(100).reshape(10,10)
 pta.add_step_through(test_array.__iter__(), 'b')

This amused me enough I saved it as a gist.

查看更多
可以哭但决不认输i
3楼-- · 2020-03-31 05:34

You do not need a loop. Redraw the new plots in the event function ontype with the command fig.canvas.draw().

import matplotlib.pyplot as plt
import numpy as np

# Define the event
def ontype(event):
    if event.key == '1':
        print 'It is working'
        vector = np.random.random(10)  
        plt.plot(vector)
        fig.canvas.draw()

# Create figure an connect the event to it
fig=plt.figure(figsize=(16,8))
plt.gcf().canvas.mpl_connect('key_press_event',ontype)

vector = np.random.random(10)  
plt.plot(vector)
plt.show()
查看更多
登录 后发表回答