0

While working on my work related project, I have learned that many GUI related topics of this awesome library are not well documented to allow an interactive program.

After many hours of collecting information and debugging, I have decided to share the following findings:

  1. How to place buttons below the plot area.
  2. How to declare handlers for both buttons and mouse clicks.
  3. How to know if a click happened at the upper plot, lower plot, or in one of the buttons.
  4. How to use the same button click handler for several buttons.
  5. How to position the window of the plot.

I'm sure this will help other programmers!

ishahak
  • 6,585
  • 5
  • 38
  • 56

2 Answers2

1

1. How to place buttons below the plot area.

That is shown in the example. The idea is to create an axes in figure coordinates, such that is below the main axes. The main axes' y coordinate is given by fig.subplotpars.bottom. So in

ax_button = plt.axes([x0, y0, width, height])

as long as y0 + height < fig.subplotpars.bottom the button will be below the axes.

2. How to declare handlers for both buttons and mouse clicks.

Buttons can register callbacks via .on_clicked. Any function inside of this will only be called if the button is clicked.

A general 'button_press_event' will need to make sure to only do something when clicked inside of an axes of choice.

def on_mouse_click(event):
    if event.inaxes == ax:
        # do something

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

3. How to know if a click happened at the upper plot, lower plot, or in one of the buttons.

Very similar to 2.: If you have 4 axes, ax1, ax2, ax_button1, ax_button2, just check the value of event.inaxes to know which axes the event happend in.

def on_mouse_click(event):
    if event.inaxes == ax1:
        # do something
    elif event.inaxes == ax2:
        # do something else

4. How to use the same button click handler for several buttons.

Similar to above you can check in which axes the event occurs.

def on_button_click(event):
    if event.inaxes == btn1.ax:
        # do something
    elif event.inaxes == btn2.ax:
        # do something else

btn1.on_clicked(on_button_click)
btn2.on_clicked(on_button_click)

Alternatively you can pass an identifier to the function

def on_button_click(event, id):
    if id == "button1":
        # do something
    elif id == "button2":
        # do something else

btn1.on_clicked(lambda e: on_button_click(e, "button1")
btn2.on_clicked(lambda e: on_button_click(e, "button2")

5. How to position the window of the plot.

This is unrelated and answered in How do you set the absolute position of figure windows with matplotlib? It will depend on which backend is in use.


Complete example:

import numpy as np
import matplotlib.pyplot as plt
from matplotlib.widgets import Button


def on_mouse_click(event):
    if event.inaxes == ax1:
        client = 'upper'
    if event.inaxes == ax2:  
        client = 'lower'
    else:
        client = 'none'
    print(client)


def on_button_click(btn):
    print(btn)
    if btn == 'Upper':
        ax1.clear()
    elif btn == 'Lower':
        ax2.clear()
    fig.canvas.draw_idle()

# First create some toy data:
x = np.linspace(0, 2*np.pi, 400)
y1 = np.sin(x**2)
y2 = np.cos(x**2)

# Creates two subplots and unpacks the output array immediately
fig, (ax1, ax2) = plt.subplots(num=1, ncols=1, nrows=2, figsize=(5,8))

ax1.plot(x, y1)
ax2.plot(x, y2)

ax1.set_title('Upper')
ax2.set_title('Lower')

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

ax_button1 = fig.add_axes([0.69, 0.01, 0.1, 0.055])
ax_button2 = fig.add_axes([0.80, 0.01, 0.1, 0.055])

btn1 = Button(ax_button1, "Upper")
btn1.on_clicked(lambda e: on_button_click("Upper"))
btn2 = Button(ax_button2, "Lower")
btn2.on_clicked(lambda e: on_button_click("Lower"))

plt.show()
ImportanceOfBeingErnest
  • 321,279
  • 53
  • 665
  • 712
  • It is a bit funny to mark your answer as accepted while it is my own knowledge sharing. I really liked your lambda solution for providing the sender id. Thank you! – ishahak Sep 11 '19 at 09:05
  • in your code please fix `if event.inaxes == ax2:` into `elif` – ishahak Sep 11 '19 at 09:18
0

The following code is based on the sample provided here: https://matplotlib.org/3.1.0/gallery/widgets/buttons.html

import numpy as np
import matplotlib.pyplot as plt
from matplotlib.widgets import Button

ax1 = ax2 = 0
ax1_first_y = ax2_first_y = 0


def on_mouse_click(event):
    if event.xdata:  # either in graph or in button
        if event.y >= ax1_first_y:
            client = 'upper'
        elif event.y >= ax2_first_y:
            client = 'lower'
        else:
            client = 'none'  # probably a button
        print('client=%s, x=%d, y=%d, xdata=%f, ydata=%f' %
              (client,
               event.x, event.y, event.xdata, event.ydata))
    else:  # no xdata, meaning clicked out of any widget
        wid = ax2.get_window_extent().width
        print('%s click: button=%d, x=%d, y=%d, p1=%.1f' %
              ('double' if event.dblclick else 'single', event.button,
               event.x, event.y, event.x/wid))


def on_button_click(event):
    txt = event.inaxes.properties()['children'][0].get_text()
    if txt == 'Upper':
        ax1.clear()
        plt.show()
    elif txt == 'Lower':
        ax2.clear()
        plt.show()
    else:
        print('unknown text: '+txt)


# First create some toy data:
x = np.linspace(0, 2*np.pi, 400)
y1 = np.sin(x**2)
y2 = np.cos(x**2)

# Creates two subplots and unpacks the output array immediately
fig, (ax1, ax2) = plt.subplots(num=1,ncols=1, nrows=2, figsize=(5,8))
ax1_first_y = ax1.transAxes.frozen().get_matrix()[1][2]
ax2_first_y = ax2.transAxes.frozen().get_matrix()[1][2]

ax1.plot(x, y1)
ax2.plot(x, y2)

ax1.set_title('Upper')
ax2.set_title('Lower')
mngr = plt.get_current_fig_manager()
mngr.set_window_title('Auto-focus Scan Results')
mngr.window.wm_geometry('+500+0')
cid = fig.canvas.mpl_connect('button_press_event', on_mouse_click)

plot_buttons = [[plt.axes([0.7, 0.01, 0.1, 0.055]), 'Upper'],
                [plt.axes([0.81, 0.01, 0.1, 0.055]), 'Lower']
                ]
all_buttons = list()  # we must keep a pointer to buttons or else they will not function correctly
for b in plot_buttons:
    btn = Button(b[0], b[1])
    btn.on_clicked(on_button_click)
    all_buttons.append(btn)

plt.show()
ishahak
  • 6,585
  • 5
  • 38
  • 56