单击 matplotlib 中的步骤图子图点时如何使标签出现(或可能是 plotly)?
Posted
技术标签:
【中文标题】单击 matplotlib 中的步骤图子图点时如何使标签出现(或可能是 plotly)?【英文标题】:How to make labels appear when clicking on a step plot subplot point in matplotlib (or possibly plotly)? 【发布时间】:2021-12-12 06:12:30 【问题描述】:我正在使用 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)
我已经测试并查看了许多答案和代码,如下所示:
How to add hovering annotations in matplotlib How to make labels appear when hovering over a point in multiple axis? ¿Es posible que aparezcan etiquetas al pasar el mouse sobre un punto en matplotlib? Matplotlib Cursor — How to Add a Cursor and Annotate Your Plot我什至审查了 mplcursors 模块很长时间,因为它带有一个与我正在做的类似的步骤图的示例:https://mplcursors.readthedocs.io/en/stable/examples/step.html,但它给了我相同的结果,我找不到解决办法。
【问题讨论】:
您是否愿意/有兴趣使用 plotly 代替?这会更容易(也更强大) 谢谢@JohnCollins。是的,当然,我只是想,如果我没记错的话,plotly 仅适用于网络,而要求我的工作适用于桌面,除此之外,此功能只是我在开发中拥有的几个功能之一,我必须改变很多,没问题,但这需要我时间,只是知道我是如何用plotly做到的? 其实没有任何 plotly 是开源的并且可以启用(我相信这甚至可能是现在的默认设置——尽管它以前不是)完全“离线”(这意味着它将使没有连接到互联网/plotly 的服务器——所以不,它不仅适用于网络)。如果有人没有打败我,我会尝试发布一个答案,仅供参考,如何以完全离线的方式使用 plotly 来解决您的问题 我会注意的。非常感谢@JohnCollins @PureRangeIEncoding 好的,已发布答案。如您所见,不必大惊小怪。正如我在我的编辑/修订描述中评论的那样,我现在正在查看文档以编辑答案,以使悬停注释仅您的“量具”数据值,据我所知,这就是您所寻求的。这绝对是可能的。 Plotly.express 自动设置悬停数据,所以我只需要看看如何撤消它。一般推荐plotly.express
,因为它简洁优雅
【参考方案1】:
鼠标悬停在图形数据点上时使用 Plotly 进行数据注释标签动画
更不用说大量其他很棒的、易于使用的、广泛兼容的 JS 交互式绘图功能,全部免费,全部使用 Python。只需使用 conda(或 pip)安装,无需在线帐户,并且最新版本中的绘图默认为“离线模式”。
所以用plotly,特别是plotly express,很简单!
就坐标轴/数据的细节而言,我并不是 100% 你想要的,但我认为下面展示了 Plotly 可用于创建交互式图表的巨大易用性,以及非常强大的自定义功能。
通过the plotly
docs 粗略阅读,您将能够轻松地将这些交互式图表调整为您想要的目的。
通过
plotly.express
,您仍然可以访问与所有其他子模块相关的内置Fig
功能。所以不要忽视那些[例如,上面的文档链接显示了特定于子图、自定义注释/悬停注释、自定义样式格式等的部分,所有这些仍然适用于plotly.express
中的对象! ])。
I - 数据结构设置
与您的相同... Plotly 旨在与
pandas.DataFrames
一起使用,特别是*。*(与
matplotlib
不同——并不是说它仍然不受欢迎!,只是... 好吧,它老化得相当糟糕,让我们面对现实吧。)
例如,
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,即:
Final | Name | Value | Gage | Id_Par | Count | Sub | Prev |
---|---|---|---|---|---|---|---|
0 | Lamp_D_Rq | 0 | F1 | 0 | 0 | 0 | 0 |
1 | Status | 4 | H1 | 0 | 0 | 1 | 0 |
2 | Status | 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 | Lamp_D_Rq | 1 | F1 | 0 | 0 | 8 | 0 |
9 | Lamp_D_Rq | 1 | H3 | 0 | 0 | 9 | 0 |
10 | Lamp_D_Rq | 3 | F1 | 0 | 0 | 10 | 0 |
11 | Lamp_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)
【讨论】:
这里是关于 plotly 是如何(现在)完全离线、免费、开源的信息,默认情况下:plotly.com/python/is-plotly-free(他们的模型是他们提供额外的“企业”解决方案来增强 API 或使用合法的专业安全用户身份验证帐户和登录等处理 devops,只需 $;但核心代码完全免费?并且可以离线工作) 哇。非常感谢您的回答@JohnCollins,当我将所有内容迁移到网络时,它将对我有很大帮助,因为我提出了这个建议,他们告诉我这听起来不错,尽管他们最初要求我将名称和子图的值,但这是一个很好的开始。 太棒了!很高兴听到它可以为您利用 Plotly 解决问题。使用相关框架(从纯 Python 代码呈现 react.js)制作 Web 应用程序 Dash 也非常强大,开源/免费(如果您可以自己处理部署 - 这很容易,恕我直言;取决于预期的用户负载当然,我想),我也强烈推荐。我在这里写了很多关于 Dash on SO 的答案。此外,对于您的子图,您可以在上面的代码中将行facet_rows="Name"
添加到 px
图中,仅此一项就会将输出分成四个图
你好@JohnCollins,你能告诉我如何删除添加facet_rows="Name"
时出现在右侧的名称的图例吗?因为当它是一个长名称时,它们会被覆盖并且无法理解;我已经搜索了该页面:plotly express reference 并查看了示例但找不到。请和谢谢。
我用labels = 'Name': '', 'Value': ''试过了,只删除了值,但名称仍然会覆盖字符串的内容,因为它会在这种情况下?【参考方案2】:
在不太了解您正在使用的库的情况下,我可以看到您正在创建这些注释对象,然后将它们分配给稍后重新分配的全局变量,因此您失去了使其可见的正确对象。
相反,您可以将注释对象保存在字典中,并在以后需要时根据对象尝试检索它们。
我用一个列表向你展示了这个想法,但我猜你需要一本字典来识别正确的对象。
我稍微修改了您的代码,如果您调整窗口大小,它会显示所需的行为...我想您还必须找到一种方法来刷新绘图:
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/编程问题,并且与您使用的库没有太大关系:)
【讨论】:
我看到你是一个新的贡献者,这个答案显示了问题的深度。为你的努力点赞! 这也会导致与 OP 状态相同的错误:AttributeError: 'Line2D' object has no attribute 'get_offsets'
,尝试单击数据点时。
是的,我也收到了AttributeError: 'Line2D' object has no attribute 'get_offsets'
的错误,但非常感谢@demorgan以上是关于单击 matplotlib 中的步骤图子图点时如何使标签出现(或可能是 plotly)?的主要内容,如果未能解决你的问题,请参考以下文章