将鼠标悬停在*具有高 y 轴值*、多条线和多个轴的图表上时显示标签

Making labels appear while hovering over plot for graphs *with high y-axis values*, multiple lines and multiple axes

我想从使用 python 制作的图表中读取值,类似于当您将鼠标悬停在数据点上时数据点值在 excel 图中的显示方式。在这里使用各种解决方案,我写了下面的代码来标记我悬停在它们上面的点。

但是,当 y 轴值较高时,我似乎无法标记某些点(这是我对为什么它不起作用的假设),也无法让方框具有纯色背景,所以我可以清楚地读取坐标(尝试将 alpha 设置为 1 但没有用)。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. 奇怪的是,当所有 y 轴值都低于 1 时,代码工作正常。

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. 在这里搜索后,我找到了 3 个有助于显示包含点坐标的框的解决方案:

  1. Possible to make labels appear when hovering over a point in matplotlib?(没有完全使用此代码,因为它用于散点图,但从此处找到了其他解决方案)
  2. (用于将之前的解决方案应用于 lines/curves)
  3. (用于将标签应用于具有多个轴的图形)

如何让方框出现在具有高 y 轴值的图表中,以及如何使方框出现在图表上方以便可以清楚地阅读?提前致谢!

本质上,您的问题是您将注释创建为属于轴 y1_axis。当您将鼠标悬停在一个点上时,您是在 y1_axis 的数据坐标中设置注释的位置,而不管该线是在该轴还是另一个轴中。

解决方案不仅要更新注释的坐标,还要更新其 transform 以正确地将点映射到正确的像素坐标。

注释的背景也是如此。由于您是在 bottom-most 轴上创建它,因此注释在这些轴的线上方,但在其他轴的线下方。这里的解决方案是在 top-most 轴中创建注释。

(...)
# 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)
(...)

完整代码:

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()