I need to trigger some data processing* after the user changes the
limits of a plot (e.g., via pan, zoom or the 'home' button). The code
below is my proof-of-concept solution to this problem, which I offer
for discussion and reference. It was a good practice in learning the
events system. I would not be sad if someone were to inform me of more
elegant solution.
Thanks,
Eric
*I'm plotting data points in three separate panels (x-y, x-z, and z-y)
with shared axes. The data processing replots x-z and z-y to match the
data in the x-y view.
class accumulator(object):
""" Provides for event callbacks for matplotlib drag/release events and
axis limit changes by accumulating a series of event occurrences.
Produces a single call to func after a user interacts with the plot.
Sample usage:
from pylab import figure, show
def simple(count):
print "update ", count
a = accumulator(simple)
f=figure()
ax=f.add_subplot(111)
plt=ax.plot(range(10))
f.canvas.mpl_connect('draw_event', a.draw_event)
f.canvas.mpl_connect('button_release_event', a.mouse_up_event)
f.canvas.mpl_connect('button_press_event', a.mouse_down_event)
ax.callbacks.connect('xlim_changed', a.axis_limit_changed)
ax.callbacks.connect('ylim_changed', a.axis_limit_changed)
show()
"""
def __init__(self, func):
self.func=func
self.reset()
self.counter = 0
self.mouse_up = False
def reset(self):
""" Reset flags after the update function is called.
Mouse is tracked separately.
"""
self.limits_changed = 0
self.got_draw = False
def axis_limit_changed(self, ax):
self.limits_changed += 1
self.check_status()
def draw_event(self, event):
self.got_draw=True
self.check_status()
def mouse_up_event(self, event):
self.mouse_up = True
self.check_status()
def mouse_down_event(self, event):
self.mouse_up = False
def both_limits_changed(self):
""" Both x and y limits changed and the mouse is up (not dragging)
This condition takes care of the limits being reset outside of a
dragging context, such as the view-reset (home) button on the
Matplotlib standard toolbar.
"""
return (self.limits_changed >= 2) & self.mouse_up
def interaction_complete(self):
""" x, y, or both limits changed, and the mouse is up (not dragging).
Also checks if matplotlib has done its final redraw of the screen,
which comes after the call to *both* set_xlim and set_ylim
have been triggered. The check for the draw event is the crucial
step in not producing two calls to self.func.
"""
return (self.limits_changed>0) & self.got_draw & self.mouse_up
def check_status(self):
if self.both_limits_changed() | self.interaction_complete():
self.func(self.counter)
self.reset()
self.counter += 1