0

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?

asked Jan 15, 2013 at 21:54

2 Answers 2

2

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.

answered Jan 16, 2013 at 5:41
9
  • Well, I will surely save this for future uses, it looks great. But still not what I need: all of this should be embebded in a for loop. I will edit the question to clarify it. Thanks! Commented Jan 16, 2013 at 10:31
  • @Álvaro, yes, that is exactly what the yield does. Commented Jan 16, 2013 at 14:13
  • Hm...it might be the solution, I will take a look. But could it be used inside an event, like the ontype one in the example? Commented Jan 16, 2013 at 14:39
  • @Álvaro I am also a physicist, so that is no excuse;) I added some print statements to ugly_math that will hopefully make it clearer what is going on when you run it. This is the easy way to do what you want (even if the logic seems inside out). Commented Jan 16, 2013 at 15:12
  • I think I am still not getting the idea behind all of this. As I understand it (not totally I feel...) is that yield returns one value, and that value is kept for the next iteration. In my case, I have a 2000 lines code with a main for loop, using approx 100 different variables and at the end of each run of this main for loop, it gets a figure. Also, just the "ontype" rutine requires 8 variables to work (this is a simplified version). The yield approach seems to requite to modify the whole program, which I will do if there is no other way, but... can it be done without this huge modification? Commented Jan 16, 2013 at 15:18
1

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()
answered Jan 15, 2013 at 22:22
4
  • Hm...the thing is I actually need the loop. In each iteration, I calculate the "array" I have to plot. That's probably the problem, but due to the way the data are given, I can't think of any other solution... But you example worked pretty well, maybe there is a way... Commented Jan 15, 2013 at 22:38
  • No, you don't need the for loop. You should calculate your data inside the 'ontype' routine. Why isn't that possible? Commented Jan 15, 2013 at 23:00
  • It might be possible, but complicated. Inside the for loop (before plotting anything), several functions and classes are used to deal and modify the data, and depending on some conditions they are treated in a way or another. The data aren't homogeneus, and it will also imply passing like twenty variables (or more) to the ontype routin So... don't know, maybe I could try to do all of that inside the ontype routine, but it feels weird that it is not possible to simply show one plot, press one button and get the for loop going. Commented Jan 15, 2013 at 23:07
  • @Álvaro Use Closures and generators. Commented Jan 16, 2013 at 5:28

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.