Issue
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.
Solution
Using Plotly for data annotation labels animation upon mouse hovering over graph data points
Not to mention a huge slew of other awesome, easy-to-use, widely-compatible JS-interactive graphing capabilities, all free, all in Python. Just install with conda (or pip) no online account required and the plots default to "offline mode" in latest version(s).
So with plotly, specifically plotly express, it's very simple!
I am not 100% what you want as far as specifics for your axes/data, but I think below demonstrates the tremendous ease with which Plotly can be used to create interactive graphs, & the very powerful customization available.
You will easily be able to adapt these interactive graphs to your desired purposes via cursory perusal through the plotly
docs.
And through
plotly.express
you still have access to the built-inFig
features relevant to all the other submodules, too. So don't overlook those [e.g., the docs link above shows sections specific for subplotting, custom annnotations/hover annotations, custom style formatting, etc., all which still apply to objects withinplotly.express
!]).
I - Data Structures Setup
Identical to yours... Plotly is designed to work with
pandas.DataFrames
, specifically*.
E.g.,
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)
NOTE: I then ran signals_df
through your plotting function, and added return signals_df
to get the updated df, which was:
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 - Plotting custom hover annotations with plotly.express
(px)
Here's one relatively (i.e., to
mpl
) quite simple, possible multi-featured, modern interactive display of your data using Plotly (viapx
):
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})
Answered By - John Collins
0 comments:
Post a Comment
Note: Only a member of this blog may post a comment.