0

I have a script which spawns a data plot with a SpanSelector widget. The widget calls a select_window(vmin, vmax) closure function, which uses the window limits to analyse the chosen data. The analysis function spawns another plot with some visual results.

The default behaviour of SpanSelector is to execute select_window immediately upon selection. Since the calculations are a bit heavy, I would like the user to confirm the selected window via keypress. The first option is to use plt.waitforbuttonpress, but this responds to all key events including those used by default for pan/zoom/etc. in matplotlib.

The second option is to connect a key_press_event directly, but I was unsure where to connect and disconnect the event handler.

Working example, using waitforbuttonpress:

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

def analyse(data, window_min, window_max):
    sliced_data = data[window_min:window_max]
    print(sliced_data)
    fig, ax = plt.subplots()
    ax.plot(sliced_data)
    plt.pause(0.001)

def plot_data(data):
    fig, ax = plt.subplots()
    ax.plot(data)
    def select_window(vmin, vmax):
        if plt.waitforbuttonpress(60):
            window_min = int(np.floor(vmin))
            window_max = int(np.ceil(vmax))
            analyse(data, window_min, window_max)
    widget = SpanSelector(
        ax, select_window, 'horizontal', useblit=True, span_stays=True,
        minspan=1
    )
    plt.show()
    return widget  # Keeping a reference so it isn't garbage collected.

if __name__ == '__main__':
    data = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
    widget = plot_data(data)
adigitoleo
  • 92
  • 1
  • 8
  • Can you make this reproducible (e.g. with dummy data, dummy functions) such that it can be run. Also see [mcve]. – ImportanceOfBeingErnest Feb 28 '20 at 10:34
  • @ImportanceOfBeingErnest Thank you, I was a bit tired when I posted this and it turns out writing a proper MWE revealed the actual issue, which I was subsequently able to fix (see below). I will leave this post here in case others find it useful. It was my first question on the site, and I appreciate the feedback. – adigitoleo Feb 29 '20 at 01:32

1 Answers1

0

The key was to use fig.canvas.mpl_connect and fig.canvas.mpl_disconnect in the correct places. The disconnect is required, otherwise the plots will accumulate on successive window selections.

This is the solution, which only accepts the enter key as valid window confirmation. Other keys can be accessed via event.key.

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

def analyse(data, window_min, window_max):
    sliced_data = data[window_min:window_max]
    print(sliced_data)
    fig, ax = plt.subplots()
    ax.plot(sliced_data)
    plt.pause(0.001)

def plot_data(data):
    fig, ax = plt.subplots()
    ax.plot(data)
    def select_window(vmin, vmax):
        def _confirm_selection(event):
            if event.key == 'enter':  # Make the selector wait for <enter> key
                window_min = int(np.floor(vmin))
                window_max = int(np.ceil(vmax))
                analyse(data, window_min, window_max)
                fig.canvas.mpl_disconnect(cid)  # Disconnect the event after analysis
        cid = fig.canvas.mpl_connect('key_press_event', _confirm_selection)
    widget = SpanSelector(
        ax, select_window, 'horizontal', useblit=True, span_stays=True,
        minspan=1
    )
    plt.show()
    return widget  # Keeping a reference so it isn't garbage collected.

if __name__ == '__main__':
    data = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
    widget = plot_data(data)

The event ID is accessible to mpl_disconnect from the closure. So, the event can be disconnected within it's own callback (i.e. _confirm_selection). I'm not sure if this is the best way, and improvements are welcome, but it does work ;)

adigitoleo
  • 92
  • 1
  • 8