You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何捕获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

原理说明

  1. 遍历Y轴全量可见元素:覆盖轴本身、轴标签、刻度线、刻度标签,确保捕捉用户可能操作的所有区域。
  2. 单独检测spine:副Y轴的spine被移动到非默认位置,默认检测逻辑不会覆盖,因此需要单独判断。
  3. 保留原X轴和绘图区检测逻辑,确保原有功能不受影响。

替换原detect_artist函数后,即可正常捕获所有Y轴(包括twinx创建的副Y轴)上的滚动事件,实现对应轴的缩放功能。

内容的提问来源于stack exchange,提问作者PBrockmann

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.15 01:05:54