As the title suggests, I am trying to put multiple SpanSelector
widgets on the same axis. In the test code below (adapted from this example), I am using a multi-plot figure with the qt backend, to zoom in on the two regions selected by the span selectors. I want to make sure that the two widgets can function independently. I have set non-overlapping initial spans for them both when the figure is generated. I also need to make sure that ignore_event_outside
is set to True
for both span selectors, so that trying to move/resize one does not re-draw the other span selector at that location. I thought this is would be enough to make sure the two widgets function independently and there is no ambiguity in which object should respond to a given event.
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.widgets import SpanSelector
# Fixing random state for reproducibility
np.random.seed(19680801)
mosaic = """AA
BC"""
fig = plt.figure(figsize=(8, 6))
ax_dict = fig.subplot_mosaic(mosaic)
(ax0,ax1,ax2) = list(ax_dict.values())
x = np.arange(0.0, 5.0, 0.01)
y = np.sin(2 * np.pi * x) + 0.5 * np.random.randn(len(x))
ax0.plot(x, y, color='black')
ax0.set_ylim(-2, 2)
ax0.set_title('Press left mouse button and drag '
'to select a region in the top graph')
line1, = ax1.plot([], [], color='dodgerblue')
line2, = ax2.plot([], [], color='coral')
def onselect1(xmin, xmax):
indmin, indmax = np.searchsorted(x, (xmin, xmax))
indmax = min(len(x) - 1, indmax)
region_x = x[indmin:indmax]
region_y = y[indmin:indmax]
if len(region_x) >= 2:
line1.set_data(region_x, region_y)
ax1.set_xlim(region_x[0], region_x[-1])
ax1.set_ylim(region_y.min(), region_y.max())
fig.canvas.draw_idle()
def onselect2(xmin, xmax):
indmin, indmax = np.searchsorted(x, (xmin, xmax))
indmax = min(len(x) - 1, indmax)
region_x = x[indmin:indmax]
region_y = y[indmin:indmax]
if len(region_x) >= 2:
line2.set_data(region_x, region_y)
ax2.set_xlim(region_x[0], region_x[-1])
ax2.set_ylim(region_y.min(), region_y.max())
fig.canvas.draw_idle()
span1 = SpanSelector(
ax0,
onselect1,
"horizontal",
useblit=True,
props=dict(alpha=0.5, facecolor="dodgerblue"),
interactive=True,
drag_from_anywhere=True,
ignore_event_outside=True
)
span2 = SpanSelector(
ax0,
onselect2,
"horizontal",
useblit=True,
props=dict(alpha=0.5, facecolor="coral"),
interactive=True,
drag_from_anywhere=True,
ignore_event_outside=True
)
# Set useblit=True on most backends for enhanced performance.
# Set default values
xmin1, xmax1 = 1.4, 2.3
span1.extents = (xmin1, xmax1)
span1.onselect(xmin1, xmax1)
xmin2, xmax2 = 2.5, 3.1
span2.extents = (xmin2, xmax2)
span2.onselect(xmin2, xmax2)
plt.show()
However, whenever I try to interact with the two widgets, things don’t go as planned. As seen in the GIF below, when I first click on one of the span selectors (red in this example) after the figure is generated, and move/resize it, the other widget (blue in this example) responds to it, even though ignore_event_outside
is set to True
. Subsequent interactions seem to work almost fine but still look strange. In this example, moving/resizing the blue span selector makes the red span selector “blink” off until the mouse button is released. Please note that the opposite does not happen. Also note that the cursor does not change (to ) when I hover over the handles for the blue span selector - I have click on the handle before the cursor shape changes. Again, the red span selector is not affected in the same way.
I know I can try to assign different mouse buttons to the two span selectors in this case, but I eventually plan to generate 3-4 span selectors on the same figure and I would simply run out of mouse buttons.
Additional info:
OS: Windows 10
Python version: 3.9.13
Matplotlib version: 3.5.3
Jupyter Notebook server version: 6.4.12
IPython version: 8.4.0