如何捕获Matplotlib中twinx创建的Y轴滚动事件?检测返回None
问题:无法捕获twinx创建的Y轴滚动事件
使用twin1 = ax.twinx()创建的副Y轴,调用ax.yaxis.contains(event)[0]检测滚动事件时返回None,无法识别鼠标在这些副Y轴上的滚动操作。
多Y轴示意图:左侧排列三个独立Y轴,分别对应密度(红色)、温度(蓝色)、速度(绿色),右侧为数据绘图区域。
问题代码
import numpy as np import matplotlib.pyplot as plt from matplotlib.axis import XAxis, YAxis #-------------------------------------------------------------- def detect_artist(event): """ Detect whether the event occurred on an axis (X or Y) or inside an Axes. Returns the detected element (XAxis, YAxis, or Axes) or None. """ for ax in axs: # Check if the event occurred on the X or Y axis ticks if ax.xaxis.contains(event)[0]: return ax.xaxis if ax.yaxis.contains(event)[0]: return ax.yaxis # Check if the event occurred inside the Axes itself if ax.contains(event)[0]: return ax return None #-------------------------------------------------------------- def on_scroll(event): """ Handle scroll events to zoom in or out on the detected axis. """ scale_factor = 0.9 if event.button == 'up' else 1.1 artist = detect_artist(event) # Detect the Artist element under the mouse print(artist) if artist is None: return # No axis detected, do nothing if isinstance(artist, XAxis): ax = artist.axes # Zoom on the X axis cur_xlim = ax.get_xlim() xdata = event.xdata if event.xdata is not None else (cur_xlim[0] + cur_xlim[1]) / 2 new_xlim = [xdata - (xdata - cur_xlim[0]) * scale_factor, xdata + (cur_xlim[1] - xdata) * scale_factor] ax.set_xlim(new_xlim) ax.figure.canvas.draw() # Redraw the canvas print(f"Scrolled on XAxis of Axes: {ax}") elif isinstance(artist, YAxis): # Find the parent Axes of this YAxis ax = artist.axes ylim = ax.get_ylim() center = (ylim[0] + ylim[1]) / 2 range_ = (ylim[1] - ylim[0]) * scale_factor / 2 ax.set_ylim(center - range_, center + range_) ax.figure.canvas.draw() # Redraw the canvas print(f"Scrolled on YAxis of Axes: {ax}") #-------------------------------------------------------------- # Data x = np.array([0, 1, 2]) y1 = np.array([0, 1, 2]) # Density y2 = np.array([0, 3, 2]) # Temperature y3 = np.array([50, 30, 15]) # Velocity # Figure and Axes fig, ax = plt.subplots(1, 1) fig.subplots_adjust(left=0.4) # Create twin axes twin1 = ax.twinx() twin1.spines["left"].set_position(("axes", -0.3)) twin1.spines["left"].set_visible(True) twin1.yaxis.set_label_position("left") twin1.yaxis.set_ticks_position("left") twin2 = ax.twinx() twin2.spines["left"].set_position(("axes", -0.5)) twin2.spines["left"].set_visible(True) twin2.yaxis.set_label_position("left") twin2.yaxis.set_ticks_position("left") # Add all axes to a list for event handling axs = [ax, twin1, twin2] # Plot data ax.plot(x, y1, color='red', label="Density") twin1.plot(x, y2, color='blue', label="Temperature") twin2.plot(x, y3, color='green', label="Velocity") # Set axis labels ax.set(xlabel="Distance", ylabel="Density") twin1.set(ylabel="Temperature") twin2.set(ylabel="Velocity") # Color labels ax.yaxis.label.set_color('red') twin1.yaxis.label.set_color('blue') twin2.yaxis.label.set_color('green') # Connect event fig.canvas.mpl_connect('scroll_event', on_scroll) plt.show()
解决方案
问题根源是contains()方法的默认检测范围:ax.yaxis.contains(event)只会检测Y轴的原始默认位置区域,但你已经通过spines["left"].set_position(("axes", -0.3))把副Y轴移到了主绘图区左侧,超出了原检测范围。
修改detect_artist函数,直接检测鼠标位置是否落在Y轴的刻度、标签或spine的显示范围内,替代默认的contains():
def detect_artist(event): """ Detect whether the event occurred on an axis (X or Y) or inside an Axes. Returns the detected element (XAxis, YAxis, or Axes) or None. """ for ax in axs: # 检测X轴 if ax.xaxis.contains(event)[0]: return ax.xaxis # 检测Y轴的所有可见元素:轴对象、标签、刻度线、刻度标签 y_axis_elements = [ax.yaxis, ax.yaxis.label] + list(ax.yaxis.get_ticklabels()) + list(ax.yaxis.get_ticklines()) for elem in y_axis_elements: if elem.contains(event)[0]: return ax.yaxis # 单独检测移动后的spine(副Y轴的spine不在默认检测范围内) for spine in ax.spines.values(): if spine.get_visible() and spine.contains(event)[0]: if spine.spine_type in ['left', 'right']: return ax.yaxis elif spine.spine_type == 'bottom': return ax.xaxis # 检测绘图区内部 if ax.contains(event)[0]: return ax return None
原理说明
- 遍历Y轴全量可见元素:覆盖轴本身、轴标签、刻度线、刻度标签,确保捕捉用户可能操作的所有区域。
- 单独检测spine:副Y轴的spine被移动到非默认位置,默认检测逻辑不会覆盖,因此需要单独判断。
- 保留原X轴和绘图区检测逻辑,确保原有功能不受影响。
替换原detect_artist函数后,即可正常捕获所有Y轴(包括twinx创建的副Y轴)上的滚动事件,实现对应轴的缩放功能。
内容的提问来源于stack exchange,提问作者PBrockmann
相关产品推荐
相关产品推荐

