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

点击matplotlib/plotly阶梯图子图点显示标注报Line2D无get_offsets错误如何解决?

问题解决

错误原因

  • plot() 方法返回的是 Line2D 类型对象,get_offsets() 是散点图返回的 PathCollection 对象的方法,二者接口不通用,直接调用必然触发 AttributeError。
  • 循环绘制子图时反复覆盖了 steps、y_gage、annot 变量,交互逻辑仅绑定了最后一个子图的配置,前面子图的点击逻辑完全失效。
  • 循环内重复创建了大量冗余的标注对象,无意义占用资源。

修改说明

  1. 改用 Line2D 的 get_xdata()、get_ydata() 方法获取点位坐标
  2. 提前定义全局唯一的可复用标注对象,初始设置为隐藏
  3. 用列表存储每个子图对应的线对象、Gage值列表,点击时判断当前触发的子图,匹配对应数据
  4. 修正交互逻辑,支持所有子图的点击触发,标注内容直接显示对应点位的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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.28 19:54:04