I wanted to read values off of the graphs that I made using python similar to how the data point values show up in an excel plot when you hover over a data point. Using various solutions here, I wrote up the below code to label the points as I hovered over them.
However, I can't seem to label some points when there are high y-axis values (that's my assumption on why it doesn't work), nor can I get the box to have a solid background so I can read the coordinates clearly (tried setting alpha to 1 but didn't work).Here's a picture of how one such point shows up far away from the actual location and with the text blocked by the curves behind it. Oddly enough, when all the y-axis values are below 1, the code worked fine.
from matplotlib import pyplot as plt
import numpy as np; np.random.seed(1)
x_data = list(range(0,30))
y1_data_a = np.sort(np.random.rand(30))
y1_data_b = np.sort(np.random.rand(30))
y1_data_c = [0.4 for point in x_data]
y2_data_a = [point**2 for point in x_data]
y2_data_b = [point*0.5 for point in y2_data_a]
y3_data = [(10/(point+1)) for point in x_data]
# #The code works fine with this data
# x_data = list(range(0,30))
# y1_data_a = np.sort(np.random.rand(30))
# y1_data_b = np.sort(np.random.rand(30))
# y1_data_c = [0.4 for point in x_data]
# y2_data_a = np.random.rand(30)
# y2_data_b = np.sort(np.random.rand(30))
# y3_data = np.sort(np.random.rand(30))[::-1]
fig, y1_axis = plt.subplots()
fig.subplots_adjust(right=0.75)
y2_axis = y1_axis.twinx()
y3_axis = y1_axis.twinx()
def make_patch_spines_invisible(ax):
ax.set_frame_on(True)
ax.patch.set_visible(False)
for sp in ax.spines.values():
sp.set_visible(False)
y3_axis.spines["right"].set_position(("axes", 1.2))
make_patch_spines_invisible(y3_axis)
y3_axis.spines["right"].set_visible(True)
plot1, = y1_axis.plot(x_data, y1_data_a, color="#000CFF", label="Temp1 (°C)")
plot2, = y1_axis.plot(x_data, y1_data_b, color="#FF5100", label="Temp2 (°C)")
plot3, = y1_axis.plot(x_data, y1_data_c, "r--", label="Critical Temp (°C)")
plot4, = y2_axis.plot(x_data, y2_data_a, color="#000000", label="Pressure1 (atm)")
plot5, = y2_axis.plot(x_data, y2_data_b, color="#17E111", label="Pressure2 (atm)")
plot6, = y3_axis.plot(x_data, y3_data, color="#D418DE", label="Volume (m3)")
y1_axis.set_xlabel("Time (hrs)")
y1_axis.set_ylabel("Temperature (°C)")
y2_axis.set_ylabel("Pressure (atm)")
y3_axis.set_ylabel("Volume (m3)")
y3_axis.yaxis.label.set_color(plot6.get_color())
tkw = dict(size=4, width=1.5)
y1_axis.tick_params(axis='y', **tkw)
y2_axis.tick_params(axis='y', **tkw)
y3_axis.tick_params(axis='y', colors=plot6.get_color(), **tkw)
y1_axis.tick_params(axis='x', **tkw)
lines = [plot1, plot2, plot4, plot5, plot6]
plt.title("Labeling data points for plots with Multiple Axes and Lines", fontdict=None, loc='center')
annot = y1_axis.annotate("", xy=(0, 0), xytext=(20, 20), textcoords="offset points",
bbox=dict(boxstyle="round", facecolor="#FFFFFF"),
arrowprops=dict(arrowstyle="->"))
annot.set_visible(False)
def update_annot(line, annot, ind):
posx, posy = [line.get_xdata()[ind], line.get_ydata()[ind]]
annot.xy = (posx, posy)
text = f'{line.get_label()}: ({posx:.2f},{posy:.2f})'
annot.set_text(text)
annot.get_bbox_patch().set_alpha(1)
def hover(event):
vis = annot.get_visible()
if event.inaxes in [y1_axis, y2_axis, y3_axis]:
for line in lines:
cont, ind = line.contains(event)
if cont:
update_annot(line, annot, ind['ind'][0])
annot.set_visible(True)
fig.canvas.draw_idle()
else:
if vis:
annot.set_visible(False)
fig.canvas.draw_idle()
fig.canvas.mpl_connect("motion_notify_event", hover)
plt.show()
The ticker on the bottom right of the plot only seems to show the values based on the last axis that has been used. After searching here, I found 3 solutions that helps display a box containing the coordinates of a point:
How do I get the box to appear in graphs with high y-axis values and how do I make the box appear over the graphs so it can be read clearly? Thanks in advance!
Essentially, your problem is that you created your annotation as belonging to the axes y1_axis
. When you were hovering over a point, you were setting the position of the annotation in the data coordinate of y1_axis
, regardless of whether the line was in that axes or another.
The solution is to update not only the coordinates of the annotation, but also its transform
to correctly map the point to the correct coordinates in pixels.
The same is true for the background of the annotation. Since you were creating it on the bottom-most axes, the annotation was above the line in these axes, but below the lines in the other axes. The solution here is to create the annotation in the top-most axes.
(...)
# annotation should be on the top axis to avoid zorder problems
annot = fig.axes[-1].annotate("", xy=(0, 0), xytext=(20, 20), textcoords="offset points",
bbox=dict(boxstyle="round", facecolor="#FFFFFF"),
arrowprops=dict(arrowstyle="->"))
annot.set_visible(False)
(...)
def update_annot(line, annot, ind):
posx, posy = [line.get_xdata()[ind], line.get_ydata()[ind]]
annot.xycoords = line.axes.transData # set the correct transform for that line
annot.xy = (posx, posy)
text = f'{line.get_label()}: ({posx:.2f},{posy:.2f})'
annot.set_text(text)
annot.get_bbox_patch().set_alpha(1)
(...)
Full code:
from matplotlib import pyplot as plt
import numpy as np; np.random.seed(1)
x_data = list(range(0,30))
y1_data_a = np.sort(np.random.rand(30))
y1_data_b = np.sort(np.random.rand(30))
y1_data_c = [0.4 for point in x_data]
y2_data_a = [point**2 for point in x_data]
y2_data_b = [point*0.5 for point in y2_data_a]
y3_data = [(10/(point+1)) for point in x_data]
# #The code works fine with this data
# x_data = list(range(0,30))
# y1_data_a = np.sort(np.random.rand(30))
# y1_data_b = np.sort(np.random.rand(30))
# y1_data_c = [0.4 for point in x_data]
# y2_data_a = np.random.rand(30)
# y2_data_b = np.sort(np.random.rand(30))
# y3_data = np.sort(np.random.rand(30))[::-1]
fig, y1_axis = plt.subplots()
fig.subplots_adjust(right=0.75)
y2_axis = y1_axis.twinx()
y3_axis = y1_axis.twinx()
def make_patch_spines_invisible(ax):
ax.set_frame_on(True)
ax.patch.set_visible(False)
for sp in ax.spines.values():
sp.set_visible(False)
y3_axis.spines["right"].set_position(("axes", 1.2))
make_patch_spines_invisible(y3_axis)
y3_axis.spines["right"].set_visible(True)
plot1, = y1_axis.plot(x_data, y1_data_a, color="#000CFF", label="Temp1 (°C)")
plot2, = y1_axis.plot(x_data, y1_data_b, color="#FF5100", label="Temp2 (°C)")
plot3, = y1_axis.plot(x_data, y1_data_c, "r--", label="Critical Temp (°C)")
plot4, = y2_axis.plot(x_data, y2_data_a, color="#000000", label="Pressure1 (atm)")
plot5, = y2_axis.plot(x_data, y2_data_b, color="#17E111", label="Pressure2 (atm)")
plot6, = y3_axis.plot(x_data, y3_data, color="#D418DE", label="Volume (m3)")
y1_axis.set_xlabel("Time (hrs)")
y1_axis.set_ylabel("Temperature (°C)")
y2_axis.set_ylabel("Pressure (atm)")
y3_axis.set_ylabel("Volume (m3)")
y3_axis.yaxis.label.set_color(plot6.get_color())
tkw = dict(size=4, width=1.5)
y1_axis.tick_params(axis='y', **tkw)
y2_axis.tick_params(axis='y', **tkw)
y3_axis.tick_params(axis='y', colors=plot6.get_color(), **tkw)
y1_axis.tick_params(axis='x', **tkw)
lines = [plot1, plot2, plot4, plot5, plot6]
plt.title("Labeling data points for plots with Multiple Axes and Lines", fontdict=None, loc='center')
# annotation should be on the top axis to avoid zorder problems
annot = fig.axes[-1].annotate("", xy=(0, 0), xytext=(20, 20), textcoords="offset points",
bbox=dict(boxstyle="round", facecolor="#FFFFFF"),
arrowprops=dict(arrowstyle="->"))
annot.set_visible(False)
def update_annot(line, annot, ind):
posx, posy = [line.get_xdata()[ind], line.get_ydata()[ind]]
annot.xycoords = line.axes.transData
annot.xy = (posx, posy)
text = f'{line.get_label()}: ({posx:.2f},{posy:.2f})'
annot.set_text(text)
annot.get_bbox_patch().set_alpha(1)
def hover(event):
vis = annot.get_visible()
if event.inaxes in [y1_axis, y2_axis, y3_axis]:
for line in lines:
cont, ind = line.contains(event)
if cont:
update_annot(line, annot, ind['ind'][0])
annot.set_visible(True)
fig.canvas.draw_idle()
else:
if vis:
annot.set_visible(False)
fig.canvas.draw_idle()
fig.canvas.mpl_connect("motion_notify_event", hover)
plt.show()