如何在matplotlib(或plotly)中单击一个阶跃图子图点时显示标签?

cbjzeqam  于 11个月前  发布在  其他
关注(0)|答案(2)|浏览(103)

我正在使用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类似,但它给了我同样的结果,我找不到解决方案。

sr4lhrrt

sr4lhrrt1#

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

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

所以用plotly,具体的plotly express,非常简单!
就轴/数据的具体细节而言,我不是100%你想要的,但我认为下面展示了Plotly可以用来创建交互式图形的巨大便利性,以及非常强大的定制功能。
通过the plotly docs,您可以轻松地将这些交互式图形调整为您想要的目的。

  • 通过plotly.express,您仍然可以访问与所有其他子模块相关的内置Fig功能。所以不要忽略这些功能[例如,上面的文档链接显示了特定于 * 子标绘,自定义annotations/hover annotations,自定义样式格式 * 等的部分,所有这些仍然适用于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|计数|子|Prev|
| --|--|--|--|--|--|--|--|
| 0 |灯_D_Rq| 0 |F1| 0 | 0 | 0 | 0 |
| 1 |地位| 4 |H1| 0 | 0 | 1 | 0 |
| 2 |地位| 4 |H3| 0 | 0 | 2 | 0 |
| 3 |HMI| 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})

ckocjqey

ckocjqey2#

在不太了解你正在使用的库的情况下,我可以看到你正在创建这些注解对象,然后将它们赋值给一个全局变量,这个全局变量稍后会被重新赋值,因此你失去了使它可见的正确对象。
相反,您可以将注解对象保存在字典中,并在以后需要时根据对象检索它们。
我使用了一个列表来向你展示这个想法,但是我想你需要一个字典来识别正确的对象。
我修改了一下你的代码,如果你调整窗口大小,它会显示所需的行为.我想你必须找到一种方法来刷新图:

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/编程问题,如果我理解正确的话,与你正在使用的库没有太大关系:)

相关问题