Events

Does anybody know how to generate and process my "private" events? I can subclass the Event() class, say, MyEvent,
with a name "my_event", and I can -
    canvas.mpl_connect('my_event', aCallback)

but then, how to fire one? (I don't want to call the callback directly).

Suppose that the interface, when show() is active launches a simulation/visualisation program which animates many things in its figure. From time to time something "special" appears, and its behaviour should be steered by those private events.

(Yes, I know that I can do it in several other ways, or write my own event-processing loop, or use directly wxPython or PyGTK instead of Matplotlib. So, I don't need the replacement solution, but just a way to fire events within Matplotlib...)

Thank you.

Jerzy Karczmarczuk
Caen, France

It is smart to simply re-use mpl’s event callback system. Luckily, in the latest release, we allowed for arbitary events to be added. To answer your question, take a look at how pick_event() is declared in backend_bases.py:

def pick_event(self, mouseevent, artist, **kwargs):
    """
    This method will be called by artists who are picked and will
    fire off :class:`PickEvent` callbacks registered listeners

    """
    s = 'pick_event'
    event = PickEvent(s, self, mouseevent, artist, **kwargs)
    self.callbacks.process(s, event)

The function that “fires” the event is “self.callbacks.process(s, event)”, where “self” is the figure canvas.

I hope this helps!
Ben Root

···

On Thu, Jan 26, 2012 at 3:24 PM, Jerzy Karczmarczuk <jerzy.karczmarczuk@…3937…> wrote:

Does anybody know how to generate and process my “private” events? I can

subclass the Event() class, say, MyEvent,

with a name “my_event”, and I can -

canvas.mpl_connect('my_event', aCallback)

but then, how to fire one? (I don’t want to call the callback directly).

Suppose that the interface, when show() is active launches a

simulation/visualisation program which animates many things in its

figure. From time to time something “special” appears, and its behaviour

should be steered by those private events.

(Yes, I know that I can do it in several other ways, or write my own

event-processing loop, or use directly wxPython or PyGTK instead of

Matplotlib. So, I don’t need the replacement solution, but just a way to

fire events within Matplotlib…)

Thank you.

Jerzy Karczmarczuk

Caen, France

Benjamin Root answers my query concerning user-generated events :

To answer your question, take a look at how pick_event() is declared in backend_bases.py:

    def pick_event(self, mouseevent, artist, **kwargs):
     ...
        self.callbacks.process(s, event)

The function that "fires" the event is "self.callbacks.process(s, event)", where "self" is the figure canvas.

Dear Ben, thank you, but this is not exactly my problem. I don't want to call the callback myself, since the event should be "fired" from within a callback. I should have been more precise. Let's distil the problem. [This is a part of my current teaching...] I did already what you suggest here...

Imagine an animation, which consists in generating a trajectory, segment after segment (say, of a planet). Classically this is a loop, but when it runs, the rest of the program is blocked. So, instead, the code behaves as a Python generator, generates just one segment, and that's all. But it "yields" something, it posts an event, put it in a queue, and somebody else, the mainloop() or similar, pops it off the queue and re-launches the callback. (With generators, it calls the .next()). No timers, as in Timer or Funct animations...

It must be "decentralized", no recursive calls. My callback from time to time creates another planet, or destroys an existent, and there are simultaneous trajectories on the screen, several concurrent events in the queue. And the system should remain reactive, interpret buttons, sliders, etc.

I know how to do this by hand, how to write my own event loop, declare a queue, and how to add to my private "virtual" event handling also the callbacks of mouse events. But this is an overkill, I repeat the functionalities which are already there, the event queue in particular.

I did it with wx. But Matplotlib protects the user from the concrete back-end, and I want to protect my students as well, so I look for a "GUI-neutral" solution.

Thanks.

Jerzy

I’m not sure if this matches your use case, but have you looked into using a coroutine. (I only recently learned about them—see presentation slides linked on this page. So obviously I’m going out of my way to find a use case :slight_smile:

I’ve attached a simple example below.

-Tony

···

On Fri, Jan 27, 2012 at 5:54 AM, Jerzy Karczmarczuk <jerzy.karczmarczuk@…3937…> wrote:

Benjamin Root answers my query concerning user-generated events :

To answer your question, take a look at how pick_event() is declared

in backend_bases.py:

def pick_event(self, mouseevent, artist, **kwargs):
 ...
    self.callbacks.process(s, event)

The function that “fires” the event is "self.callbacks.process(s,

event)", where “self” is the figure canvas.

Dear Ben, thank you, but this is not exactly my problem. I don’t want to

call the callback myself, since the event should be “fired” from within

a callback. I should have been more precise. Let’s distil the problem.

[This is a part of my current teaching…] I did already what you

suggest here…

Imagine an animation, which consists in generating a trajectory, segment

after segment (say, of a planet). Classically this is a loop, but when

it runs, the rest of the program is blocked. So, instead, the code

behaves as a Python generator, generates just one segment, and that’s

all. But it “yields” something, it posts an event, put it in a queue,

and somebody else, the mainloop() or similar, pops it off the queue and

re-launches the callback. (With generators, it calls the .next()). No

timers, as in Timer or Funct animations…

It must be “decentralized”, no recursive calls. My callback from time to

time creates another planet, or destroys an existent, and there are

simultaneous trajectories on the screen, several concurrent events in

the queue. And the system should remain reactive, interpret buttons,

sliders, etc.

I know how to do this by hand, how to write my own event loop, declare a

queue, and how to add to my private “virtual” event handling also the

callbacks of mouse events. But this is an overkill, I repeat the

functionalities which are already there, the event queue in particular.

I did it with wx. But Matplotlib protects the user from the concrete

back-end, and I want to protect my students as well, so I look for a

“GUI-neutral” solution.

Thanks.

Jerzy

#~~~

import matplotlib.pyplot as plt
import numpy as np

def datalistener():
fig, ax = plt.subplots()
line, = ax.plot(0, 0, ‘o-’)

i = 1
while True:
    y0 = yield
    x = np.hstack((line.get_xdata(), i))
    y = np.hstack((line.get_ydata(), y0))
    line.set_data((x, y))
    ax.update_datalim(np.transpose((x, y)))

    ax.autoscale_view()
    plt.draw()
    i += 1

plt.ion()
plotdata = datalistener()

initialize the coroutine

plotdata.next()
while True:
y = raw_input('enter data point: ')

try:
    plotdata.send(float(y))
except:
    break

Tony Yu suggests that my multiple and changing animation problems could be solved using coroutining.

have you looked into using a coroutine. /... /I've attached a simple example below.

import matplotlib.pyplot as plt
import numpy as np
def datalistener():
...
    while True:
        y0 = yield
        x = np.hstack((line.get_xdata(), i))
  ...
plt.ion()
...

Thank you Tony. No, unfortunately this is again a "side solution". Of course, coroutines, or just a simple event loop using Python generators without .send(), are perfectly decent tools to simulate parallel "movements". But, coroutines as such, are better adapted to situations where the actors transfer the control among themselves, where there is a network of direct communications, a "multi-ping-pong".
In my case this is redundant, my "planets" which move concurrently are independent, and they, after having updated their properties, need only to yield the control to the interface, to draw them. The interface resumes (by calling, no .send() is needed) all the actors in sequence. I repeat again : I COULD have written my own event loop (under ion()).

But I don't want to do this, since show() which calls some lower level mainloop() does all this already!

In particular, it handles the physical events, all mousing, which need anyway some lower-level mechanisms, and my own coroutines won't help. I MUST use the built-in tools in order to handle the mouse.

So, I repeat, my only [as I see it] rational choice is to plug-in my events into the standard loop. Under, say, plain wxPython, I can write my own, and call ProcessPendingEvents(), but with Matplotlib, no idea.
Ben Root suggested to look some existing codes, say the Picker. But this is called upon a physical event, which is "fired" under the hood. My events are "virtual", I have to post them myself from a callback, so it canot call recursively another callback, or I am dead.

Best regards.

Jerzy

Still not sure why my suggestion would not work:

http://matplotlib.sourceforge.net/api/cbook_api.html#matplotlib.cbook.CallbackRegistry

You can have things set up such that event being triggered can subsequently trigger other events as well. An event can even connect a new event as well. An example where I do this is when I want to hide a line while it is clicked. In the function that I connect to a “button_press_event”, I make the artist invisible, and I also create a lambda function that sets the visibility of that artist to True (and subsequently disconnects itself) and connect that lambda to a “button_release_event”.

Keep in mind that these “events” are merely key’ed by strings like “button_press_event”, there is nothing magical about them. So, when you call the .process() function with a string value and an Event object, it passes that object to every connection that was made with that same string value. You can call the process() function from anywhere, even within an event.

Now, I haven’t tried connecting a generator function with a yield statement before, so I don’t know how well that would work, but I don’t know why it wouldn’t.

Why exactly would the callback registry not work for you?

Ben Root

···

On Fri, Jan 27, 2012 at 4:54 AM, Jerzy Karczmarczuk <jerzy.karczmarczuk@…3937…> wrote:

Benjamin Root answers my query concerning user-generated events :

To answer your question, take a look at how pick_event() is declared

in backend_bases.py:

def pick_event(self, mouseevent, artist, **kwargs):
 ...
    self.callbacks.process(s, event)

The function that “fires” the event is "self.callbacks.process(s,

event)", where “self” is the figure canvas.

Dear Ben, thank you, but this is not exactly my problem. I don’t want to

call the callback myself, since the event should be “fired” from within

a callback. I should have been more precise. Let’s distil the problem.

[This is a part of my current teaching…] I did already what you

suggest here…

Imagine an animation, which consists in generating a trajectory, segment

after segment (say, of a planet). Classically this is a loop, but when

it runs, the rest of the program is blocked. So, instead, the code

behaves as a Python generator, generates just one segment, and that’s

all. But it “yields” something, it posts an event, put it in a queue,

and somebody else, the mainloop() or similar, pops it off the queue and

re-launches the callback. (With generators, it calls the .next()). No

timers, as in Timer or Funct animations…

It must be “decentralized”, no recursive calls. My callback from time to

time creates another planet, or destroys an existent, and there are

simultaneous trajectories on the screen, several concurrent events in

the queue. And the system should remain reactive, interpret buttons,

sliders, etc.

I know how to do this by hand, how to write my own event loop, declare a

queue, and how to add to my private “virtual” event handling also the

callbacks of mouse events. But this is an overkill, I repeat the

functionalities which are already there, the event queue in particular.

I did it with wx. But Matplotlib protects the user from the concrete

back-end, and I want to protect my students as well, so I look for a

“GUI-neutral” solution.

Thanks.

Jerzy

Benjamin Root about my miserable event problem :

Still not sure why my suggestion would not work:

  [http://matplotlib.sourceforge.net/api/cbook_api.html#matplotlib.cbook.CallbackRegistry](http://matplotlib.sourceforge.net/api/cbook_api.html#matplotlib.cbook.CallbackRegistry)
I thought I told you. Probably I am doing something utterly false,

but my distilled problem is that ** I am generating events from
within a callback**. Here you are a complete skeleton program.

  `from pylab import *`

  `from matplotlib.cbook import *`

  `from matplotlib.widgets import Button```

  `fig=figure()`

  `ax = fig.add_subplot(1,1,1)`

  `xis=axis([0,1,0,1])`

  `subplots_adjust(left=0.1, bottom=0.1)`

  `clrax = axes([0.67, 0.02, 0.08, 0.04])`

  `clbut = Button(clrax, 'Clear', color="1.0")`

  `goax = axes([0.88, 0.02, 0.08, 0.04])`

  `gobut = Button(goax, 'Go', color="#40ffa0")`

  `cbacks = CallbackRegistry()`



  `def line(*evt): `

  `    plot(rand(2),rand(2)) ; draw()`

  `    cbacks.process('line_', None)`



  `linv  = cbacks.connect('line_', line)`



  `def clr(ev=None): del ax.lines[:]`

  `clbut.on_clicked(clr)`

  `def start(evt): cbacks.process('line_', None)`

  `gobut.on_clicked(start)`



  `subplot(1,1,1)  `

  `show()`
The function line() is the main iterative engine, a "loop" without

looping. It should post an event which re-launches it, and still
leaving the master loop active, so I can clear the figure.

Nope. This is a ***recursive call***.

Python bombs after a while, recursive limit exceeded, and only then

the Clear button wakes up.

Thank you for your effort.

Jerzy

Jerzy,

So, where is your terminating condition? Of course it will continuously call itself if you have nothing to stop it. Protect that call to “process” with some sort of if-statement and use “evt” to carry some sort of state that can be tested in that if-statement.

For example, I modified your example to use “evt” to carry an integer. The call to process() is protected by an if-statement that checks to see if evt is less than 10, and the call to process passes “evt + 1”. The initial call to process passes a value of zero. The program then produces 10 random lines just fine when I press “Go”.

Does that help clear up your problem?

Ben Root

···

On Sat, Jan 28, 2012 at 10:31 AM, Jerzy Karczmarczuk <jerzy.karczmarczuk@…3937…> wrote:

Benjamin Root about my miserable event problem :

Still not sure why my suggestion would not work:

  [http://matplotlib.sourceforge.net/api/cbook_api.html#matplotlib.cbook.CallbackRegistry](http://matplotlib.sourceforge.net/api/cbook_api.html#matplotlib.cbook.CallbackRegistry)
I thought I told you. Probably I am doing something utterly false,

but my distilled problem is that ** I am generating events from
within a callback**. Here you are a complete skeleton program.

  `from pylab import *`

  `from matplotlib.cbook import *`

  `from matplotlib.widgets import Button```

  `fig=figure()`

  `ax = fig.add_subplot(1,1,1)`

  `xis=axis([0,1,0,1])`

  `subplots_adjust(left=0.1, bottom=0.1)`

  `clrax = axes([0.67, 0.02, 0.08, 0.04])`

  `clbut = Button(clrax, 'Clear', color="1.0")`

  `goax = axes([0.88, 0.02, 0.08, 0.04])`

  `gobut = Button(goax, 'Go', color="#40ffa0")`

  `cbacks = CallbackRegistry()`



  `def line(*evt): `

  `    plot(rand(2),rand(2)) ; draw()`

  `    cbacks.process('line_', None)`



  `linv  = cbacks.connect('line_', line)`



  `def clr(ev=None): del ax.lines[:]`

  `clbut.on_clicked(clr)`

  `def start(evt): cbacks.process('line_', None)`

  `gobut.on_clicked(start)`



  `subplot(1,1,1)  `

  `show()`
The function line() is the main iterative engine, a "loop" without

looping. It should post an event which re-launches it, and still
leaving the master loop active, so I can clear the figure.

Nope. This is a ***recursive call***.

Python bombs after a while, recursive limit exceeded, and only then

the Clear button wakes up.

Thank you for your effort.



Jerzy