I'm using matplotlib to make step graphs based on a dataframe, but I want one of the key/value of the dataframe to appear (signals_df['Gage']
), instead of coordinates as annotation, but I always get the error: AttributeError: 'Line2D' object has no attribute 'get_offsets'
when I click on the first subplot from bottom to top and the annotation does not appear. In fact, I commented out the annot.set_visible(False)
and replaced the ""
of the examples with val_gage
, so that it will look like I want the annotation to appear one by one, when clicking on some point within the subplots. This is the code in question:
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)
I've tested and reviewed many answers and code like the following:
- 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
I even reviewed the mplcursors module for a long time, since it comes with an example with a graph of steps similar to what I'm doing: https://mplcursors.readthedocs.io/en/stable/examples/step.html, but it gives me the same result and I can't find the solution.
from How to make labels appear when clicking on a step plot subplot point in matplotlib?
No comments:
Post a Comment