如何在单击 matplotlib 中的步骤图子图点(或可能是绘图)时显示标签?

问题描述 投票:0回答:2

我正在使用 matplotlib 基于数据框制作步骤图,但我希望出现数据框的键/值之一(

signals_df['Gage']
),而不是坐标作为注释,但我总是收到错误:
AttributeError: 'Line2D' object has no attribute 'get_offsets' 
当我单击从下到上的第一个子图时,注释没有出现。事实上,我注释掉了
annot.set_visible(False)
,并将示例中的
""
替换为
val_gage
,这样当单击子图中的某个点时,它看起来就像我希望注释一一出现。 这是有问题的代码:

import pandas as pd
import numpy as np
import matplotlib as mtpl
from matplotlib import pyplot as plt
import matplotlib.ticker as ticker

annot = mtpl.text.Annotation

data = {
    # 'Name': ['Status', 'Status', 'HMI', 'Allst', 'Drvr', 'CurrTUBand', 'RUSource', 'RUReqstrPriority', 'RUReqstrSystem', 'RUResReqstStat', 'CurrTUBand', 'DSP', 'SetDSP', 'SetDSP', 'DSP', 'RUSource', 'RUReqstrPriority', 'RUReqstrSystem', 'RUResReqstStat', 'Status', 'Delay', 'Status', 'Delay', 'HMI', 'Status', 'Status', 'HMI', 'DSP'],
    # 'Value': [4, 4, 2, 1, 1, 1, 0, 7, 0, 4, 1, 1, 3, 0, 3, 0, 7, 0, 4, 1, 0, 1, 0, 1, 4, 4, 2, 3],
    # 'Gage': ['H1', 'H3', 'H3', 'H3', 'H3', 'H3', 'H3', 'H3', 'H3', 'H3', 'H3', 'H3', 'H3', 'H3', 'H3', 'H3', 'H3', 'H3', 'H3', 'H1', 'H1', 'H3', 'H3', 'H3', 'H1', 'H3', 'H3', 'H3'],
    # 'Id_Par': [0, 0, 0, 0, 0, 0, 10, 10, 10, 10, 10, 0, 0, 22, 22, 28, 28, 28, 28, 0, 0, 38, 38, 0, 0, 0, 0, 0]
    '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')

    # 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])
        # print(y_gage)

        # 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')
        # Labeling the markers with Values and 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)

            # Creating values in text inside the plot
            ax[pos].text(x=xy[0], y=xy[1], s=str(xy[1]), color='k', fontweight='bold', fontsize=12)

            for val_gage, xy in zip(y_gage, xy_temp):
                annot = ax[pos].annotate(val_gage, xy=xy, xytext=(-20, 20), textcoords="offset points",
                                         bbox=dict(boxstyle="round", fc="w"),
                                         arrowprops=dict(arrowstyle="->"))
                # annot.set_visible(False)

    # Function for storing and showing the clicked values
    def update_annot(ind):
        print("Enter update_annot")
        coord = steps[0].get_offsets()[ind["ind"][0]]
        annot.xy = coord
        text = "{}, {}".format(" ".join(list(map(str, ind["ind"]))),
                                " ".join([y_gage[n] for n in ind["ind"]]))
        annot.set_text(text)
        annot.get_bbox_patch().set_alpha(0.4)

    def on_click(event):
        print("Enter on_click")
        vis = annot.get_visible()
        # print(event.inaxes)
        # print(ax[pos])
        # print(event.inaxes == ax[pos])
        if event.inaxes == ax[pos]:
            cont, ind = steps[0].contains(event)
            if cont:
                update_annot(ind)
                annot.set_visible(True)
                fig.canvas.draw_idle()
            else:
                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)

我已经测试并审查了许多答案和代码,如下所示:

我什至审查了 mplcursors 模块很长时间,因为它附带了一个示例,其中的步骤图与我正在做的类似:https://mplcursors.readthedocs.io/en/stable/examples/step。 html,但它给了我相同的结果,但我找不到解决方案。

python python-3.x matplotlib plotly plotly-python
2个回答
1
投票

使用 Plotly 进行鼠标悬停在图形数据点上时的数据注释标签动画

更不用说大量其他很棒的、易于使用的、广泛兼容的 JS 交互式绘图功能,全部免费,全部使用 Python。只需使用 conda(或 pip)安装,无需在线帐户,最新版本中的绘图默认为“离线模式”。


所以用plotly,具体的plotly表达,就很简单了!

对于您的轴/数据的具体细节,我并不是 100% 您想要的,但我认为下面演示了 Plotly 可用于创建交互式图表的巨大轻松,以及可用的非常强大的自定义功能。

通过粗略浏览

plotly
文档,您将能够轻松地将这些交互式图表调整为您想要的目的。

通过

plotly.express
,您仍然可以访问与所有其他子模块相关的内置
Fig
功能。因此,不要忽视这些[例如,上面的文档链接显示了特定于 subplotting、自定义注释/悬停注释、自定义样式格式 等的部分,所有这些仍然适用于
plotly.express
中的对象!])。

I - 数据结构设置

与您的相同...Plotly 设计是为了与
pandas.DataFrames
合作,特别是*。

例如,

import plotly.express as px
import plotly.graph_objs as go

import pandas as pd
import numpy as np

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)

注意: 然后我通过绘图函数运行

signals_df
,并添加
return signals_df
来获取更新后的 df,即:

决赛 姓名 价值 量规 Id_Par 上一页
0 灯_D_Rq 0 F1 0 0 0 0
1 状态 4 H1 0 0 1 0
2 状态 4 H3 0 0 2 0
3 人机界面 2 H3 11 1 2 1
4 Lck_D_RqDrv3 1 H3 0 0 4 0
5 Lck_D_RqDrv3 1 F1 0 0 5 0
6 Lck_D_RqDrv3 2 H3 0 0 6 0
7 Lck_D_RqDrv3 2 F1 0 0 7 0
8 灯_D_Rq 1 F1 0 0 8 0
9 灯_D_Rq 1 H3 0 0 9 0
10 灯_D_Rq 3 F1 0 0 10 0
11 灯_D_Rq 3 H3 0 0 11 0

II - 使用
plotly.express
(px)

绘制自定义悬停注释

这是一种相对(即
mpl
)非常简单、可能的多功能、现代交互式数据显示,使用 Plotly(通过
px
):

fig = px.line(
    signals_df,
    y="Value",
    x="Sub",
    color="Name",
    hover_data=["Gage"],
    custom_data=["Gage"],
    markers=True,
    height=500,
    render_mode="svg")

fig.update_traces(line={"shape": 'hv'})
fig.update_traces(
    hovertemplate="<br>".join([
        "Gage: %{customdata[0]}",
    ])
)
fig.show(config={'displaylogo': False})


1
投票

在不了解您正在使用的库的情况下,我可以看到您正在创建这些注释对象,然后将它们分配给稍后重新分配的全局变量,因此您丢失了正确的对象以使其可见。

相反,您可以将注释对象保存到字典中,并在以后根据对象需要它们时尝试检索它们。

我用一个列表来向你展示这个想法,但我想你需要一本字典来识别正确的对象。

我稍微修改了你的代码,如果你调整窗口大小,它会显示所需的行为......我想你也必须找到一种刷新绘图的方法:

import pandas as pd
import numpy as np
import matplotlib as mtpl
from matplotlib import pyplot as plt
import matplotlib.ticker as ticker

annotations = []
data = {
    # 'Name': ['Status', 'Status', 'HMI', 'Allst', 'Drvr', 'CurrTUBand', 'RUSource', 'RUReqstrPriority', 'RUReqstrSystem', 'RUResReqstStat', 'CurrTUBand', 'DSP', 'SetDSP', 'SetDSP', 'DSP', 'RUSource', 'RUReqstrPriority', 'RUReqstrSystem', 'RUResReqstStat', 'Status', 'Delay', 'Status', 'Delay', 'HMI', 'Status', 'Status', 'HMI', 'DSP'],
    # 'Value': [4, 4, 2, 1, 1, 1, 0, 7, 0, 4, 1, 1, 3, 0, 3, 0, 7, 0, 4, 1, 0, 1, 0, 1, 4, 4, 2, 3],
    # 'Gage': ['H1', 'H3', 'H3', 'H3', 'H3', 'H3', 'H3', 'H3', 'H3', 'H3', 'H3', 'H3', 'H3', 'H3', 'H3', 'H3', 'H3', 'H3', 'H3', 'H1', 'H1', 'H3', 'H3', 'H3', 'H1', 'H3', 'H3', 'H3'],
    # 'Id_Par': [0, 0, 0, 0, 0, 0, 10, 10, 10, 10, 10, 0, 0, 22, 22, 28, 28, 28, 28, 0, 0, 38, 38, 0, 0, 0, 0, 0]
    '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')

    # Loop to build the different graphs
    for pos, name in enumerate(names_list):
        print("name: %s" % name)
        print("pos: %s" % pos)
        # 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])
        # print(y_gage)

        # 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')
        # Labeling the markers with Values and 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)

            # Creating values in text inside the plot
            ax[pos].text(x=xy[0], y=xy[1], s=str(xy[1]), color='k', fontweight='bold', fontsize=12)

            for val_gage, xy in zip(y_gage, xy_temp):
                print("val_gage: %s" % val_gage)
                annot = ax[pos].annotate(val_gage, xy=xy, xytext=(-20, 20), textcoords="offset points",
                                         bbox=dict(boxstyle="round", fc="w"),
                                         arrowprops=dict(arrowstyle="->"))

                annot.set_visible(False)
                annotations.append(annot)

    # Function for storing and showing the clicked values
    def update_annot(ind):
        print("Enter update_annot")
        coord = steps[0].get_offsets()[ind["ind"][0]]
        annot.xy = coord
        text = "{}, {}".format(" ".join(list(map(str, ind["ind"]))),
                                " ".join([y_gage[n] for n in ind["ind"]]))
        annot.set_text(text)
        annot.get_bbox_patch().set_alpha(0.4)

    def on_click(event):
        print("Enter on_click")
        vis = annot.get_visible()
        # make the first three annotations visible
        for i in range(0, 3):
            print('elem visible')
            annotations[i].set_visible(True)
        print(event.inaxes)
        print(ax[pos])
        print(event.inaxes == ax[pos])
        if event.inaxes == ax[pos]:
            cont, ind = steps[0].contains(event)
            print (ind)
            if cont:
                update_annot(ind)
                annot.set_visible(True)
                fig.canvas.draw_idle()
            else:
                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)

我希望这会有所帮助并解决您的问题。如果我猜对了,它看起来更像是一个 python/编程问题,并且与您正在使用的库没有太大关系:)

© www.soinside.com 2019 - 2024. All rights reserved.