3

I took the following steps to setup an IPython backend in Google Colab notebook:

!pip install ipympl
from google.colab import output
output.enable_custom_widget_manager()  

Then I log the (x,y) location of the user's click on a figure:

%matplotlib ipympl
import matplotlib
import matplotlib.pyplot as plt

fig, ax = plt.subplots()

def onclick(event):
    ix, iy = event.xdata, event.ydata
    print(ix, iy)

cid = fig.canvas.mpl_connect('button_press_event', onclick)

I need the code to wait here until the user selects at least one data point. However, the code will run if I have another command in the same notebook, for example:

print ('done!')

will run without waiting for the user to pick a data point. I tried using this before:

plt.waitforbuttonpress()
print('done!')

However, the compiler gets stuck at plt.waitforbuttonpress() and it doesn't let the user click the figure. Thanks for your help

Mohammad
  • 1,078
  • 2
  • 18
  • 39

2 Answers2

2

You could, instead of putting the code you want to run after a button_press_event, after the event listener, you could instead put it in the onclick function. Something like this:

%matplotlib ipympl
import matplotlib
import matplotlib.pyplot as plt

fig, ax = plt.subplots()

def onclick(event):
    ix, iy = event.xdata, event.ydata
    print(ix, iy)
    print('done!')

cid = fig.canvas.mpl_connect('button_press_event', onclick)

Or:

%matplotlib ipympl
import matplotlib
import matplotlib.pyplot as plt

fig, ax = plt.subplots()

def after_onclick():
    print('done!')
def onclick(event):
    ix, iy = event.xdata, event.ydata
    print(ix, iy)
    after_onclick()

cid = fig.canvas.mpl_connect('button_press_event', onclick)

The reason for this is because mpl_connect works rather oddly (see this question). Instead of waiting for the event to be registered and pausing the code execution, it will run the entire file but keep the event listener open.

The problem with this way is that it will run the code every time the event is registered.

If that is not what you want and you only want it to run once try this:

%matplotlib ipympl
import matplotlib
import matplotlib.pyplot as plt

fig, ax = plt.subplots()

def after_onclick():
    print('done')
    fig.canvas.mpl_disconnect(cid)
def onclick(event):
    ix, iy = event.xdata, event.ydata
    print(ix, iy)
    after_onclick()

cid = fig.canvas.mpl_connect('button_press_event', onclick)

This version adds fig.canvas.mpl_disconnect(cid) so that the code will only run once.

catasaurus
  • 933
  • 4
  • 20
1

If instead of Colab you are running a normal notebook locally e.g. in VSCode, you can also use figure.ginput(n=1) to wait for a user click in a blocking way.

fig, ax = plt.subplots()

clicks = fig.ginput(n=1)
print('Done:', clicks)

Output:

Done: [(9.070766129032256, 0.17960461218983856)]

Tested this on Jupyter + VSCode and it works, creating a window for you to click on.

Marco Bonelli
  • 63,369
  • 21
  • 118
  • 128