如何在Matplotlib中点击交互图将坐标存入对应多列表
问题描述
将三组数据绘制在同一张Matplotlib图中,希望通过交互式鼠标点击图上的点,将这些点的坐标存入各自对应的列表zradius、zangle、zamp中。目前可以点击图上的点并显示坐标,但无法将值存入定义的列表中,请问需修改哪些内容以适配多列表存储需求?
原代码
import matplotlib.pyplot as plt import numpy as np from mpldatacursor import datacursor # 假设实际数据定义 actual_x = np.random.rand(10)*100 actual_y = np.random.rand(10)*100 actual_z = np.linspace(100,700,10) amp0 = np.random.rand(10)*100 fig1, ax = plt.subplots() fig1 = plt.figure(figsize= (10,10)) radius = np.sqrt(actual_x**2 + actual_y**2) angle = np.arctan2(actual_y,actual_x) angle = angle/0.01745 # 弧度转角度 amplitudez = amp0/10. for i in range(len(actual_x)): ax.scatter([actual_z],[radius], color = 'black', label='ID: Z: {:.2f},Radius: {:.2f}'.format(actual_z[i],radius[i]),marker = '*',s = 20) ax.scatter([actual_z],[angle], color = 'green', label='ID: Z: {:.2f},Angle: {:.2f}'.format(actual_z[i],angle[i]),marker = '*',s = 20) ax.scatter([actual_z],[amplitudez], color = 'brown', label='ID: Z: {:.2f},AmpZ: {:.2f}'.format(actual_z[i],amplitudez[i]),marker = 'x',s = 20) ax.set_title('Experimental Data event:') ax.set_xlabel('Z values') ax.set_ylabel('Y/X values') ax.set_xlim(100,700) ax.grid() datacursor(formatter='{label}'.format, bbox=dict(fc='yellow', alpha =10.0, snap =True)) zradius, zangle, zamp = [], [], [] def onclick(event): global iz, iradius, iangle, iamplitudez iz, iradius, iangle, iamplitudez = event.zdata, event.rdata, event.adata, event.ampdata global zradius, zangle, zamp zradius.append((iz, iradius)) zangle.append((iz, iangle)) zamp.append((iz, iamplitudez)) if len(zradius) == len(actual_x): fig.canvas.mpl_disconnect(cid) return zradius, zangle, zamp cid = fig.canvas.mpl_connect('button_press_event', onclick)
原输出
In[7]: zradius Out[7]: []
问题分析与修改方案
核心问题
- 图形对象混乱:原代码先创建
fig1, ax = plt.subplots(),随后又重新赋值fig1 = plt.figure(figsize=(10,10)),导致后续绑定事件的画布对象与实际显示的画布不匹配,点击事件根本无法触发。 - 事件属性错误:Matplotlib的
button_press_event事件对象没有zdata、rdata这类自定义属性,仅能通过xdata、ydata获取点击位置的坐标轴坐标。 - 未区分点击点组别:原代码没有判断点击的点属于哪一组数据,无法将数据对应存入目标列表。
修改后的完整代码
import matplotlib.pyplot as plt import numpy as np from mpldatacursor import datacursor # 实际数据定义 actual_x = np.random.rand(10)*100 actual_y = np.random.rand(10)*100 actual_z = np.linspace(100,700,10) amp0 = np.random.rand(10)*100 # 统一创建画布与坐标轴 fig1, ax = plt.subplots(figsize=(10,10)) radius = np.sqrt(actual_x**2 + actual_y**2) angle = np.arctan2(actual_y, actual_x) / 0.01745 # 弧度转角度 amplitudez = amp0 / 10. # 保存每组散点对象,用于判断点击归属 scatter_radius = [] scatter_angle = [] scatter_amp = [] for i in range(len(actual_x)): # 绘制radius组并保存对象 s_r = ax.scatter(actual_z[i], radius[i], color='black', label=f'ID: Z: {actual_z[i]:.2f},Radius: {radius[i]:.2f}', marker='*', s=20) scatter_radius.append(s_r) # 绘制angle组并保存对象 s_a = ax.scatter(actual_z[i], angle[i], color='green', label=f'ID: Z: {actual_z[i]:.2f},Angle: {angle[i]:.2f}', marker='*', s=20) scatter_angle.append(s_a) # 绘制amplitudez组并保存对象 s_amp = ax.scatter(actual_z[i], amplitudez[i], color='brown', label=f'ID: Z: {actual_z[i]:.2f},AmpZ: {amplitudez[i]:.2f}', marker='x', s=20) scatter_amp.append(s_amp) ax.set_title('Experimental Data event:') ax.set_xlabel('Z values') ax.set_ylabel('Y/X values') ax.set_xlim(100,700) ax.grid() datacursor(formatter='{label}'.format, bbox=dict(fc='yellow', alpha=0.8, snap=True)) zradius, zangle, zamp = [], [], [] def onclick(event): # 仅处理坐标轴内的有效点击 if event.inaxes != ax: return click_x, click_y = event.xdata, event.ydata # 匹配radius组点 for i, s in enumerate(scatter_radius): x_data, y_data = s.get_offsets()[0] if np.sqrt((click_x - x_data)**2 + (click_y - y_data)**2) < 5: zradius.append((actual_z[i], radius[i])) print(f"已添加radius点: Z={actual_z[i]:.2f}, Radius={radius[i]:.2f}") return # 匹配angle组点 for i, s in enumerate(scatter_angle): x_data, y_data = s.get_offsets()[0] if np.sqrt((click_x - x_data)**2 + (click_y - y_data)**2) < 5: zangle.append((actual_z[i], angle[i])) print(f"已添加angle点: Z={actual_z[i]:.2f}, Angle={angle[i]:.2f}") return # 匹配amplitudez组点 for i, s in enumerate(scatter_amp): x_data, y_data = s.get_offsets()[0] if np.sqrt((click_x - x_data)**2 + (click_y - y_data)**2) < 5: zamp.append((actual_z[i], amplitudez[i])) print(f"已添加amp点: Z={actual_z[i]:.2f}, AmpZ={amplitudez[i]:.2f}") return # 所有点收集完成后断开事件 if len(zradius) == len(actual_x) and len(zangle) == len(actual_x) and len(zamp) == len(actual_x): fig1.canvas.mpl_disconnect(cid) # 绑定事件到正确的画布对象 cid = fig1.canvas.mpl_connect('button_press_event', onclick) plt.show()
修改说明
- 统一画布对象:用
plt.subplots(figsize=(10,10))一次性创建画布和坐标轴,避免重复创建导致的对象不匹配。 - 保存散点对象:绘制每组数据时,将散点实例存入列表,后续通过计算点击位置与散点的距离判断点击归属。
- 有效点击判断:先过滤坐标轴外的无效点击,再通过距离阈值(此处设为5)判断是否点击到目标点。
- 分组存储逻辑:根据点击点的组别,将对应坐标存入
zradius、zangle或zamp列表。
内容的提问来源于stack exchange,提问作者theheretic
相关产品推荐
相关产品推荐

