点击matplotlib/plotly阶梯图子图点显示标注报Line2D无get_offsets错误如何解决?
问题解决
错误原因
plot()方法返回的是Line2D类型对象,get_offsets()是散点图返回的PathCollection对象的方法,二者接口不通用,直接调用必然触发AttributeError。- 循环绘制子图时反复覆盖了
steps、y_gage、annot变量,交互逻辑仅绑定了最后一个子图的配置,前面子图的点击逻辑完全失效。 - 循环内重复创建了大量冗余的标注对象,无意义占用资源。
修改说明
- 改用
Line2D的get_xdata()、get_ydata()方法获取点位坐标 - 提前定义全局唯一的可复用标注对象,初始设置为隐藏
- 用列表存储每个子图对应的线对象、Gage值列表,点击时判断当前触发的子图,匹配对应数据
- 修正交互逻辑,支持所有子图的点击触发,标注内容直接显示对应点位的Gage值
完整可运行代码
import pandas as pd import numpy as np import matplotlib as mtpl from matplotlib import pyplot as plt import matplotlib.ticker as ticker data = { 'Name': ['Lamp_D_Rq', 'Status', 'Status', 'HMI', 'Lck_D_RqDrv3', 'Lck_D_RqDrv3', 'Lck_D_RqDrv3', 'Lck_D_RqDrv3', 'Lamp_D_Rq', 'Lamp_D_Rq', 'Lamp_D_Rq', 'Lamp_D_Rq'], 'Value': [0, 4, 4, 2, 1, 1, 2, 2, 1, 1, 3, 3], 'Gage': ['F1', 'H1', 'H3', 'H3', 'H3', 'F1', 'H3', 'F1', 'F1', 'H3', 'F1', 'H3'], 'Id_Par': [0, 0, 0, 11, 0, 0, 0, 0, 0, 0, 0, 0] } signals_df = pd.DataFrame(data) def plot_signals(signals_df): print(signals_df) # Count signals by parallel signals_df['Count'] = signals_df.groupby('Id_Par').cumcount().add(1).mask(signals_df['Id_Par'].eq(0), 0) # Subtract Parallel values from the index column signals_df['Sub'] = signals_df.index - signals_df['Count'] id_par_prev = signals_df['Id_Par'].unique() id_par = np.delete(id_par_prev, 0) signals_df['Prev'] = [1 if x in id_par else 0 for x in signals_df['Id_Par']] signals_df['Final'] = signals_df['Prev'] + signals_df['Sub'] # Convert and set Subtract to index signals_df.set_index('Final', inplace=True) # Get individual names and variables for the chart names_list = [name for name in signals_df['Name'].unique()] num_names_list = len(names_list) num_axisx = len(signals_df["Name"]) # Matplotlib's categorical feature to convert x-axis values to string x_values = [-1, ] x_values += (list(set(signals_df.index))) x_values = [str(i) for i in sorted(x_values)] # Creation Graphics fig, ax = plt.subplots(nrows=num_names_list, figsize=(10, 10), sharex=True) plt.xticks(np.arange(0, num_axisx), color='SteelBlue', fontweight='bold') # 存储每个子图对应的线对象、Gage值列表 line_list = [] gage_list = [] ax_list = ax.flatten() # 全局唯一标注,初始隐藏 annot = mtpl.text.Annotation("", xy=(0,0), xytext=(-20, 20), textcoords="offset points", bbox=dict(boxstyle="round", fc="w"), arrowprops=dict(arrowstyle="->")) annot.set_visible(False) fig.add_artist(annot) # Loop to build the different graphs for pos, name in enumerate(names_list): # Creating a dummy plot and then remove it dummy, = ax[pos].plot(x_values, np.zeros_like(x_values)) dummy.remove() # Get names by values and gage data data = signals_df[signals_df["Name"] == name]["Value"] data_gage = signals_df[signals_df["Name"] == name]["Gage"] # Get values axis-x and axis-y x_ = np.hstack([-1, data.index.values, len(signals_df) - 1]) y_ = np.hstack([0, data.values, data.iloc[-1]]) y_gage = np.hstack(["", "-", data_gage.values]) # Plotting the data by position steps = ax[pos].plot(x_.astype('str'), y_, drawstyle='steps-post', marker='*', markersize=8, color='k', linewidth=2) ax[pos].set_ylabel(name, fontsize=8, fontweight='bold', color='SteelBlue', rotation=30, labelpad=35) ax[pos].yaxis.set_major_formatter(ticker.FormatStrFormatter('%0.1f')) ax[pos].yaxis.set_tick_params(labelsize=6) ax[pos].grid(alpha=0.4, color='SteelBlue') # 存储当前子图的线对象和Gage值 line_list.append(steps[0]) gage_list.append(y_gage) # 显示点位数值 xy_temp = [] for i in range(len(y_)): if i == 0: xy = [x_[0].astype('str'), y_[0]] xy_temp.append(xy) else: xy = [x_[i - 1].astype('str'), y_[i - 1]] xy_temp.append(xy) ax[pos].text(x=xy[0], y=xy[1], s=str(xy[1]), color='k', fontweight='bold', fontsize=12) # Function for storing and showing the clicked values def update_annot(line, gage_data, ind): x = line.get_xdata()[ind["ind"][0]] y = line.get_ydata()[ind["ind"][0]] annot.xy = (x, y) text = gage_data[ind["ind"][0]] annot.set_text(text) annot.get_bbox_patch().set_alpha(0.4) def on_click(event): vis = annot.get_visible() # 遍历所有子图判断点击位置 for ax_idx, current_ax in enumerate(ax_list): if event.inaxes == current_ax: line = line_list[ax_idx] gage_data = gage_list[ax_idx] cont, ind = line.contains(event) if cont: update_annot(line, gage_data, ind) annot.set_visible(True) fig.canvas.draw_idle() return # 点击点位外隐藏标注 if vis: annot.set_visible(False) fig.canvas.draw_idle() fig.canvas.mpl_connect("button_press_event",on_click) plt.show() plot_signals(signals_df)
内容的提问来源于stack exchange,提问作者MayEncoding
相关产品推荐
相关产品推荐

