Bas Swinckels
Bas Swinckels

Reputation: 18488

Zoom stops working after pressing '1' button, but only in subplot

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

Answers (1)

ImportanceOfBeingErnest
ImportanceOfBeingErnest

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

Related Questions