I have created a 2 plot subplot. Selecting the range in both subplots works fine. Clicking into the upper subplot (the zoomed plot) shifts both views fine, but when clicking in the lower (total view) plot, the selected range (SpanSelector) region vanishes. What am I missing.
As a next step I intend to ass a cross-hair cursor to the top (zoomed) plot.
Attaches the code-example:
import sys
import numpy as np
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
import matplotlib.pyplot as plt
from matplotlib.widgets import MultiCursor
from PyQt5.QtWidgets import QMainWindow,QVBoxLayout
from PyQt5.QtWidgets import QApplication
from PyQt5 import QtCore, QtGui, QtWidgets
from matplotlib.ticker import FuncFormatter
from matplotlib.widgets import SpanSelector
import matplotlib.ticker as ticker
class MainWindow_code_serarch(object):
def setup_code_serarch(self, MainWindow):
MainWindow.setObjectName("MainWindow")
MainWindow.resize(1024, 800)
self.centralwidget = QtWidgets.QWidget(MainWindow)
self.centralwidget.setObjectName("centralwidget")
self.verticalLayoutWidget = QtWidgets.QWidget(self.centralwidget)
self.verticalLayoutWidget.setGeometry(QtCore.QRect(0, 0, 1024, 800))
self.verticalLayoutWidget.setObjectName("verticalLayoutWidget")
self.verticalLayout = QtWidgets.QVBoxLayout(self.verticalLayoutWidget)
self.verticalLayout.setContentsMargins(0,0,0,0)
self.verticalLayout.setObjectName("verticalLayout")
self.figure = plt.figure()
self.canvas = FigureCanvas(self.figure)
self.verticalLayout.addWidget(self.canvas)
axes, axes2 = self.figure.subplots(nrows=2, sharex=True)
datacount = 100000
data_x = []
data_y = []
for i in range(1, datacount):
data_x.append(i)
if i % 250 <= 50:
data_y.append(np.nan)
else:
data_y.append(np.sin(i/100)+0.05*np.sin(i))
#** matplotlib subplot *********************************************
self.figure.subplots_adjust(left=0.03, right=0.97, top=0.975, bottom=0.075, wspace=0, hspace=0.2)
ax1, ax2 = self.figure.subplots(2, height_ratios=[0.8,0.2])
ax1.grid()
ax1.tick_params(axis = 'x', length=0)
ax1.tick_params(axis = 'y', length=0)
ax2.tick_params(axis = 'x', length=0)
ax2.tick_params(axis = 'y', length=0)
ax2.grid()
def format_tick_labels(x, pos):
return '{0:2.0e}'.format(x)
ax2.xaxis.set_major_formatter(FuncFormatter(format_tick_labels))
ax2.plot(data_x, data_y, linewidth = 0.5)
ax2.set_xlim(0, datacount)
ax2.set_ylim(-1.2, 1.2)
self.line2, = ax1.plot([], [], linewidth=0.5)
def onselect_span(xmin, xmax):
print('onselect_span')
indmin, indmax = np.searchsorted(data_x, (xmin, xmax))
print(f'Span: select indmin = {indmin}, indmax = {indmax}')
region_x = data_x[indmin:indmax]
region_y = data_y[indmin:indmax]
if len(region_x)>=2:
self.line2.set_data(region_x, region_y)
ax1.set_xlim(region_x[0], region_x[-1]) #select from region start till region end...
ax1.set_ylim(-1.2,1.2)#region_y[0], region_y[-1])
self.span2.extents = (region_x[0], region_x[-1])
self.canvas.draw_idle()
self.mouse_SpanSelected = True;
else:
print('onselect_span: region too small')
self.span1.clear()
print('onselect_span end')
self.span1 = SpanSelector(
ax1,
onselect_span,
"horizontal",
useblit=True,
props=dict(alpha=0.3, facecolor="tab:red"),
interactive=True,
drag_from_anywhere=True,
grab_range = 3,
)
self.span2 = SpanSelector(
ax2,
onselect_span,
"horizontal",
useblit=True,
props=dict(alpha=0.3, facecolor="tab:red"),
interactive=True,
drag_from_anywhere=True,
grab_range = 3,
)
def onclick(event):
global ix
ix = event.xdata
print('onclick end')
def onrelease(event):
print('onrelease')
global ix, ixrel
ixrel = event.xdata
if abs(ix-ixrel)<=2:
print('Release: region too small')
width_half = int ((self.line2._x[-1] - self.line2._x[0])/2)
self.span2.extents = (ixrel - width_half, ixrel + width_half)
onselect_span(ixrel - width_half, ixrel + width_half)
self.span2.update()
self.canvas.draw_idle()
print('onrelease end')
click_id = self.figure.canvas.mpl_connect('button_press_event', onclick)
relaese_id = self.figure.canvas.mpl_connect('button_release_event', onrelease)
self.span2.extents = (4000,15501) #set selectred region
onselect_span(4000,15501)
self.canvas.draw()
MainWindow.setCentralWidget(self.centralwidget)
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
MainWindow = QtWidgets.QMainWindow()
ui = MainWindow_code_serarch()
ui.setup_code_serarch(MainWindow)
MainWindow.show()
sys.exit(app.exec_())
Image: at start: this is how it looks after starting the app when I click in the upper graph the range shifts when I click in the lower graph the range disappears => this is my problem
The thing is that when you click, that is like selecting an empty span (I take that you are already aware of that, since you instrument the code with a lot of print
that tell you exactly what happen, with what values).
And SpanSelector is removed when you select an empty span. And this occurs even before you had a chance to call your callbacks.
(I have a vague feeling, but very vague, I didn't take time to understand why you did all these things with a callback not only for select
but also for click and release, that you are recoding what SpanSelector is already doing).
You can prevent that removal by playing with parameter minspan
of SpanSelector
s. Make it minspan=-1
, and then they will never be removed. Sure, it is a aberration theoretically (default minspan=0
is supposed to be the least value that makes sense), but in your case, since you will then do what it takes to put it back if needed...
At any rate, it seems to be working like I think you expect
So, my minimal reproducible answer :-) (it is only a copy of your question, with two additional minspan=-1
)
import sys
import numpy as np
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
import matplotlib.pyplot as plt
from matplotlib.widgets import MultiCursor
from PyQt5.QtWidgets import QMainWindow,QVBoxLayout
from PyQt5.QtWidgets import QApplication
from PyQt5 import QtCore, QtGui, QtWidgets
from matplotlib.ticker import FuncFormatter
from matplotlib.widgets import SpanSelector
import matplotlib.ticker as ticker
class MainWindow_code_serarch(object):
def setup_code_serarch(self, MainWindow):
MainWindow.setObjectName("MainWindow")
MainWindow.resize(1024, 800)
self.centralwidget = QtWidgets.QWidget(MainWindow)
self.centralwidget.setObjectName("centralwidget")
self.verticalLayoutWidget = QtWidgets.QWidget(self.centralwidget)
self.verticalLayoutWidget.setGeometry(QtCore.QRect(0, 0, 1024, 800))
self.verticalLayoutWidget.setObjectName("verticalLayoutWidget")
self.verticalLayout = QtWidgets.QVBoxLayout(self.verticalLayoutWidget)
self.verticalLayout.setContentsMargins(0,0,0,0)
self.verticalLayout.setObjectName("verticalLayout")
self.figure = plt.figure()
self.canvas = FigureCanvas(self.figure)
self.verticalLayout.addWidget(self.canvas)
axes, axes2 = self.figure.subplots(nrows=2, sharex=True)
datacount = 100000
data_x = []
data_y = []
for i in range(1, datacount):
data_x.append(i)
if i % 250 <= 50:
data_y.append(np.nan)
else:
data_y.append(np.sin(i/100)+0.05*np.sin(i))
#** matplotlib subplot *********************************************
self.figure.subplots_adjust(left=0.03, right=0.97, top=0.975, bottom=0.075, wspace=0, hspace=0.2)
ax1, ax2 = self.figure.subplots(2, height_ratios=[0.8,0.2])
ax1.grid()
ax1.tick_params(axis = 'x', length=0)
ax1.tick_params(axis = 'y', length=0)
ax2.tick_params(axis = 'x', length=0)
ax2.tick_params(axis = 'y', length=0)
ax2.grid()
def format_tick_labels(x, pos):
return '{0:2.0e}'.format(x)
ax2.xaxis.set_major_formatter(FuncFormatter(format_tick_labels))
ax2.plot(data_x, data_y, linewidth = 0.5)
ax2.set_xlim(0, datacount)
ax2.set_ylim(-1.2, 1.2)
self.line2, = ax1.plot([], [], linewidth=0.5)
def onselect_span(xmin, xmax):
print('onselect_span')
indmin, indmax = np.searchsorted(data_x, (xmin, xmax))
print(f'Span: select indmin = {indmin}, indmax = {indmax}')
region_x = data_x[indmin:indmax]
region_y = data_y[indmin:indmax]
if len(region_x)>=2:
self.line2.set_data(region_x, region_y)
ax1.set_xlim(region_x[0], region_x[-1]) #select from region start till region end...
ax1.set_ylim(-1.2,1.2)#region_y[0], region_y[-1])
self.span2.extents = (region_x[0], region_x[-1])
self.canvas.draw_idle()
self.mouse_SpanSelected = True;
else:
print('onselect_span: region too small')
self.span1.clear()
print('onselect_span end')
self.span1 = SpanSelector(
ax1,
onselect_span,
"horizontal",
useblit=True,
props=dict(alpha=0.3, facecolor="tab:red"),
interactive=True,
drag_from_anywhere=True,
minspan=-1, ## <<<<<<<< HERE
grab_range = 3,
)
self.span2 = SpanSelector(
ax2,
onselect_span,
"horizontal",
useblit=True,
props=dict(alpha=0.3, facecolor="tab:red"),
interactive=True,
drag_from_anywhere=True,
minspan=-1, ## <<<<<<<< AND HERE
grab_range = 3,
)
def onclick(event):
global ix
ix = event.xdata
print('onclick end')
def onrelease(event):
print('onrelease')
global ix, ixrel
ixrel = event.xdata
if abs(ix-ixrel)<=2:
print('Release: region too small')
width_half = int ((self.line2._x[-1] - self.line2._x[0])/2)
self.span2.extents = (ixrel - width_half, ixrel + width_half)
onselect_span(ixrel - width_half, ixrel + width_half)
self.span2.update()
self.canvas.draw_idle()
print('onrelease end')
click_id = self.figure.canvas.mpl_connect('button_press_event', onclick)
relaese_id = self.figure.canvas.mpl_connect('button_release_event', onrelease)
self.span2.extents = (4000,15501) #set selectred region
onselect_span(4000,15501)
self.canvas.draw()
MainWindow.setCentralWidget(self.centralwidget)
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
MainWindow = QtWidgets.QMainWindow()
ui = MainWindow_code_serarch()
ui.setup_code_serarch(MainWindow)
MainWindow.show()
sys.exit(app.exec_())