Reputation: 18488
I am developing a simple GUI to analyze some data using Matplotlib's own event handling. This works great in general, but I have been scratching my head for the last hours over some weird bug.
Code below is a heavily chopped down version of my real application. The way it works is that you can click on the image to add and remove points by clicking with left and right mouse clicks. This 'editing mode' can be switched on and off by pressing the '0' and '1' keys.
This works fine when the image is the only subplot. The bug is that if the image is one out of several subplots, Matplotlib's zoom button stops working as soon as you switch on the editing mode (by pressing the '1' button, which installs the on_click function as a handler for the button_press_event). After this, pressing 'o' or the zoom button changes the cursor, but the rectangle for the zoom area does no longer appear. I guess the handler somehow messes with Matplotlib's internals.
Does anyone understand why the zoom keeps working in case of a single subplot, but stops working when installing an on_click handler in case of multiple subplots?. To change between working and buggy version comment/uncomment the indicated lines that generate the figure and the subplot(s). I am using python 2.7.14 and matplotlib 2.2.2, both from anaconda.
import numpy as np
import matplotlib.pyplot as plt
class Points(object):
def __init__(self, ax):
self.ax = ax
# make dummy plot, points will be added later
self.dots, = ax.plot([], [], '.r')
self.x = []
self.y = []
def onclick(self, event):
print 'point.onclick'
# only handle clicks in the relevant axis
if event.inaxes is not self.ax:
print 'outside axis'
return
# add point with left button
if event.button == 1:
self.x.append(event.xdata)
self.y.append(event.ydata)
# delete point with right button
if event.button == 3 and len(self.x) > 0:
imn = np.argmin((event.xdata - self.x)**2 + (event.ydata - self.y)**2)
del self.x[imn]
del self.y[imn]
self.dots.set_data(self.x, self.y)
plt.draw()
#### THIS WORKS ####
fig, ax3 = plt.subplots()
####################
#### THIS DOES NOT WORK ####
# fig, [[ax1, ax2], [ax3, ax4]] = plt.subplots(2, 2)
############################
ax3.imshow(np.random.randn(10, 10))
ax3.set_title('Press 0/1 to disable/enable editing points')
points = Points(ax3)
# initial state
cid_click = None
state = 0
def on_key(event):
global cid_click, state
print 'you pressed %r' % event.key
if event.key in '01':
if cid_click is not None:
print 'disconnect cid_click', cid_click
fig.canvas.mpl_disconnect(cid_click)
cid_click = None
state = int(event.key)
if state:
print 'connect'
cid_click = fig.canvas.mpl_connect('button_press_event', points.onclick)
# plt.draw()
print 'state = %i, cid = %s' % (state, cid_click)
cid_key = fig.canvas.mpl_connect('key_press_event', on_key)
plt.show()
Upvotes: 1
Views: 358
Reputation: 339250
This is one of the weirdest bugs I've encountered so far. The problem is related to pressing the 1 key. This, for some unknown reason seems to kill the callback for other events. I created a bug report about it.
For now, the solution would be to not use the 1 key, but e.g. the a/d keys (for activate/deactivate).
# initial state
cid_click = None
state = "d"
def on_key(event):
global cid_click, state
print 'you pressed %r' % event.key
if event.key in 'ad':
if cid_click is not None:
print 'disconnect cid_click', cid_click
fig.canvas.mpl_disconnect(cid_click)
cid_click = None
state = event.key
if state == "a":
print 'connect'
cid_click = fig.canvas.mpl_connect('button_press_event', points.onclick)
print 'state = %s, cid = %s' % (state, cid_click)
Upvotes: 1