matplotlib部件(widgets)之范围选区(SpanSelector)

本文介绍Matplotlib中范围选区的功能与实现方法,通过两个案例演示如何使用SpanSelector类进行数据选择及实现数据缩放效果。

范围选区概述

范围选区是一种常见的对象选择方式,在一个子图中,可以在某一个轴方向上用鼠标选择起始范围的数据,这个特性可用来实现数据缩放(datazoom)。在matplotlib中的范围选区属于部件(widgets),matplotlib中的部件都是中性(neutral )的,即与具体后端实现无关。

范围选区具体实现定义为matplotlib.widgets.SpanSelector类,继承关系为:Widget->AxesWidget->_SelectorWidget->SpanSelector

SpanSelector类的签名为class matplotlib.widgets.SpanSelector(ax, onselect, direction, minspan=None, useblit=False, rectprops=None, onmove_callback=None, span_stays=False, button=None)

SpanSelector类构造函数的参数为:

  • ax:范围选区生效的子图,类型为matplotlib.axes.Axes的实例。
  • onselect:范围选区完成后执行的回调函数,函数签名为def func(min, max)minmax的数据类型均为浮点数,表示选区在某个坐标轴方向上的最小值和最大值。
  • direction:范围选区的坐标轴方向,取值范围为{"horizontal", "vertical"},类型为字符串。
  • minspan:选区的最小范围,选区范围小于minspan将不会触发onselect
  • rectprops:范围选区的外观的属性,默认值为None
  • span_stays:布尔值,是否在鼠标释放后仍然保留选区,默认值为False
  • onmove_callback:当选区确定后移动鼠标触发的回调函数,函数签名为def func(min, max)minmax的数据类型均为浮点数,默认值为None

SpanSelector类中的state_modifier_keys公有变量 state_modifier_keys定义了操作快捷键,类型为字典。

案例:范围选取基本实现

官方案例一,范围选区基本实现。

案例说明

在这里插入图片描述
单击鼠标拖动到适当位置释放鼠标形成范围选区,选区为透明度0.5蓝色,控制台输出选区在横坐标轴上的最大、最小坐标。
控制台输出:

1.569758064516129 2.0044354838709677

案例代码

import matplotlib.pyplot as plt
import matplotlib.widgets as mwidgets
fig, ax = plt.subplots()
ax.plot([1, 2, 3], [10, 50, 100])
def onselect(vmin, vmax):
    print(vmin, vmax)
rectprops = dict(facecolor='blue', alpha=0.5)
span = mwidgets.SpanSelector(ax, onselect, 'horizontal',span_stays=True,
                             rectprops=rectprops)
plt.show()

案例:数据缩放功能

官方案例,https://matplotlib.org/gallery/widgets/span_selector.html

案例说明

在这里插入图片描述
在上方子图单击鼠标拖动到适当位置释放鼠标形成范围选区,选区为红色,下方子图重绘为选定区域内数据系列,起到了数据放大的效果。

案例代码

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

# Fixing random state for reproducibility
np.random.seed(19680801)

fig, (ax1, ax2) = plt.subplots(2, figsize=(8, 6))
ax1.set(facecolor='#FFFFCC')

x = np.arange(0.0, 5.0, 0.01)
y = np.sin(2*np.pi*x) + 0.5*np.random.randn(len(x))

ax1.plot(x, y, '-')
ax1.set_ylim(-2, 2)
ax1.set_title('Press left mouse button and drag to test')

ax2.set(facecolor='#FFFFCC')
line2, = ax2.plot(x, y, '-')


def onselect(xmin, xmax):
    indmin, indmax = np.searchsorted(x, (xmin, xmax))
    indmax = min(len(x) - 1, indmax)

    thisx = x[indmin:indmax]
    thisy = y[indmin:indmax]
    line2.set_data(thisx, thisy)
    ax2.set_xlim(thisx[0], thisx[-1])
    ax2.set_ylim(thisy.min(), thisy.max())
    fig.canvas.draw()
    
span = SpanSelector(ax1, onselect, 'horizontal', useblit=True, span_stays=True,
                    rectprops=dict(alpha=0.5, facecolor='red'))
# Set useblit=True on most backends for enhanced performance.

plt.show()
<think>我们将为每个子图添加一个水平范围选择工具(SpanSelector),这样用户可以在图表上选择一段横轴范围来放大查看。 注意:由于我们有多个子图,我们需要为每个子图单独设置SpanSelector,并且需要编写相应的onselect函数。 同时,由于SpanSelector是交互式工具,我们还需要考虑如何重置视图(比如添加一个按钮或右键点击事件)。 这里我们为每个子图添加SpanSelector,当用户选择一个范围时,我们将该子图的X轴设置为选择的范围。 同时,我们添加一个键盘事件(如按空格键)来重置所有子图的视图到全局范围。 由于代码较长,我们只修改绘图部分,其他部分保持不变。 步骤: 1. 在创建子图后,为每个子图创建一个SpanSelector。 2. 编写onselect函数,该函数将设置当前子图的x轴范围。 3. 添加键盘事件监听,当按下空格键时,重置所有子图到全局X轴范围。 注意:SpanSelector需要导入:from matplotlib.widgets import SpanSelector 我们将修改代码,在绘图循环中为每个子图添加SpanSelector,并保存这些选择器的引用(以免被垃圾回收)。 同时,我们保存全局X轴范围(global_min_x, global_max_x)用于重置。 由于SpanSelector的onselect函数会接收两个参数(min, max),我们使用lambda函数或闭包来捕获当前子图(ax)。 但是注意:在循环中创建lambda函数时,要避免闭包变量延迟绑定问题。因此,我们使用一个工厂函数来创建回调。 另一种方法是使用functools.partial,但这里我们使用一个辅助函数。 我们创建一个函数`onselect(min, max, ax)`,然后使用lambda: onselect(min, max, ax) 但这样在循环中会出错,因为ax是循环变量。 所以,我们可以在循环内使用一个临时变量,然后定义一个内部函数。 实际上,我们可以这样:在循环内,为每个ax定义一个独立的onselect函数,但这样代码会重复。我们采用另一种方式:将ax作为参数传入,然后返回一个固定ax的回调。 我们使用lambda ax=ax: ... 来绑定当前循环中的ax。 此外,我们还需要一个重置函数,用于响应键盘事件,将所有的子图x轴范围重置为全局范围。 代码修改如下: </think> # 在Matplotlib中添加SpanSelector实现区域选择功能 `SpanSelector` 是 Matplotlib 提供的交互式工具,允许用户在图表上选择水平或垂直区域。以下是如何在您的代码中添加SpanSelector并实现区域选择功能的完整解决方案: ```python import pandas as pd import matplotlib.pyplot as plt import os import glob import numpy as np import matplotlib.font_manager as fm from pathlib import Path from matplotlib.widgets import SpanSelector import matplotlib.dates as mdates # 设置中文字体支持 def set_chinese_font(): try: font_path = None possible_fonts = [ 'C:/Windows/Fonts/simhei.ttf', 'C:/Windows/Fonts/msyh.ttc', '/System/Library/Fonts/PingFang.ttc', '/usr/share/fonts/truetype/droid/DroidSansFallbackFull.ttf' ] for font in possible_fonts: if Path(font).exists(): font_path = font break if font_path: font_prop = fm.FontProperties(fname=font_path) plt.rcParams['font.family'] = font_prop.get_name() else: plt.rcParams['font.family'] = 'sans-serif' plt.rcParams['font.sans-serif'] = ['Arial Unicode MS', 'SimHei', 'DejaVu Sans'] plt.rcParams['axes.unicode_minus'] = False except Exception as e: print(f"字体设置错误: {e}") # 调用字体设置函数 set_chinese_font() # 1. 读取多个Excel文件 excel_files = glob.glob(r'C:\Users\yang.liu\Desktop\负极验证12.22\*.xlsx') if not excel_files: raise FileNotFoundError("未找到任何Excel文件") # 存储处理后的DataFrame和A列数据 processed_data = {} all_a_columns = [] for file in excel_files: try: df = pd.read_excel(file) file_name = os.path.basename(file) # 添加VE列 (G/D*100) df['VE'] = np.where(df.iloc[:, 3] != 0, (df.iloc[:, 6] / df.iloc[:, 3]) * 100, np.nan) processed_data[file_name] = df all_a_columns.append(df.iloc[:, 0]) except Exception as e: print(f"处理文件 {file} 时出错: {e}") if not processed_data: raise ValueError("没有有效的数据可处理") # 2. 找出最长的A列并确定全局X轴范围 longest_a_col = max(all_a_columns, key=len) global_min_x = min(col.min() for col in all_a_columns) global_max_x = max(col.max() for col in all_a_columns) # 添加5%的缓冲区域 buffer = (global_max_x - global_min_x) * 0.05 global_min_x -= buffer global_max_x += buffer # 3. 创建三个散点图 fig, axes = plt.subplots(3, 1, figsize=(12, 15)) fig.subplots_adjust(bottom=0.15) # 为按钮留出空间 # 存储所有SpanSelector实例 spans = [] # 绘图配置 plot_config = [ {'col_idx': 3, 'ylabel': 'CE(%)', 'ax': axes[0]}, {'col_idx': 6, 'ylabel': 'EE(%)', 'ax': axes[1]}, {'col_idx': -1, 'ylabel': 'VE(%)', 'ax': axes[2]} ] # 为每个子图创建SpanSelector def onselect(min_val, max_val, ax): """当选择区域时更新轴范围""" ax.set_xlim(min_val, max_val) plt.draw() for config in plot_config: ax = config['ax'] col_idx = config['col_idx'] for file_name, df in processed_data.items(): y_data = df.iloc[:, col_idx] valid_indices = ~y_data.isna() x_data = df.iloc[:, 0] valid_x = x_data.loc[valid_indices.values] valid_y = y_data.loc[valid_indices.values] ax.scatter(valid_x, valid_y, label=file_name, alpha=0.7, s=30) ax.set_xlabel('循环次数 (Cycle #)') ax.set_ylabel(config['ylabel']) ax.set_xlim(global_min_x, global_max_x) ax.grid(True) ax.legend() # 添加SpanSelector到每个子图 span = SpanSelector( ax, lambda min_val, max_val, ax=ax: onselect(min_val, max_val, ax), 'horizontal', useblit=True, rectprops=dict(alpha=0.3, facecolor='blue') # 选择区域的样式 ) spans.append(span) # 保留引用,防止垃圾回收 # 添加重置按钮 def reset_view(event): """重置所有轴的范围""" for config in plot_config: config['ax'].set_xlim(global_min_x, global_max_x) plt.draw() # 创建按钮位置 button_ax = plt.axes([0.81, 0.05, 0.12, 0.04]) # [x, y, width, height] button = plt.Button(button_ax, '重置视图', color='lightgoldenrodyellow', hovercolor='0.975') # 连接按钮点击事件 button.on_clicked(reset_view) # 添加键盘快捷键 def on_key(event): """键盘事件处理""" if event.key == 'r': # 按R键重置视图 reset_view(event) elif event.key == 'escape': # 按ESC键退出 plt.close() fig.canvas.mpl_connect('key_press_event', on_key) # 4. 保存图像 plt.tight_layout() plt.savefig('output_plots.png', dpi=300, bbox_inches='tight') plt.show() ``` ## 代码详细解释 ### 1. SpanSelector功能实现 ```python # 为每个子图创建SpanSelector def onselect(min_val, max_val, ax): """当选择区域时更新轴范围""" ax.set_xlim(min_val, max_val) plt.draw() for config in plot_config: # ... [其他代码] ... # 添加SpanSelector到每个子图 span = SpanSelector( ax, lambda min_val, max_val, ax=ax: onselect(min_val, max_val, ax), 'horizontal', useblit=True, rectprops=dict(alpha=0.3, facecolor='blue') # 选择区域的样式 ) spans.append(span) # 保留引用,防止垃圾回收 ``` - `SpanSelector` 需要两个主要参数:轴对象和选择回调函数 - `onselect` 函数接收选择的最小值和最大值,然后设置轴的X范围 - 使用 `lambda` 函数将当前轴传递给回调函数 - `rectprops` 定义选择区域的视觉效果(透明度、颜色) - 将SpanSelector实例存储在列表中,防止被Python垃圾回收机制回收 ### 2. 添加重置功能 ```python # 添加重置按钮 def reset_view(event): """重置所有轴的范围""" for config in plot_config: config['ax'].set_xlim(global_min_x, global_max_x) plt.draw() # 创建按钮位置 button_ax = plt.axes([0.81, 0.05, 0.12, 0.04]) button = plt.Button(button_ax, '重置视图', color='lightgoldenrodyellow', hovercolor='0.975') # 连接按钮点击事件 button.on_clicked(reset_view) ``` - 创建重置函数,将所有轴的范围恢复为全局范围 - 在图表底部添加一个"重置视图"按钮 - 按钮有正常的颜色和悬停颜色,提高用户体验 ### 3. 添加键盘快捷键 ```python # 添加键盘快捷键 def on_key(event): """键盘事件处理""" if event.key == 'r': # 按R键重置视图 reset_view(event) elif event.key == 'escape': # 按ESC键退出 plt.close() fig.canvas.mpl_connect('key_press_event', on_key) ``` - 添加键盘事件处理函数 - 按"R"键重置视图 - 按"ESC"键关闭图表窗口 ### 4. 其他优化 ```python # 添加5%的缓冲区域 buffer = (global_max_x - global_min_x) * 0.05 global_min_x -= buffer global_max_x += buffer # 调整图表布局 fig.subplots_adjust(bottom=0.15) # 为按钮留出空间 ``` - 在全局范围添加5%的缓冲,使数据点不完全贴边 - 调整图表底部边距,为按钮留出空间 ## 使用说明 1. **区域选择**: - 在图表中点击并水平拖动以选择区域 - 释放鼠标后,图表将自动缩放到选定区域 2. **视图重置**: - 点击"重置视图"按钮恢复原始视图 - 或按键盘"R"键重置视图 3. **退出**: - 按键盘"ESC"键关闭图表窗口 4. **保存图像**: - 图表显示时,可以右键点击保存当前视图 - 程序会自动保存初始视图为"output_plots.png" ## 扩展功能建议 1. **添加缩放历史**: - 实现后退/前进按钮,记录缩放历史 - 允许用户在多级缩放中导航 2. **区域数据导出**: - 添加功能将选定区域的数据导出为CSV - 显示选定区域的统计信息(平均值、标准差等) 3. **多图表联动**: - 使所有子图同步缩放 - 在一个图表中选择区域时,其他图表自动同步
评论 2
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值