首页 > 解决方案 > 将标签悬停在图表上时显示标签*具有高 y 轴值*、多条线和多轴

问题描述

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

但是,当 y 轴值很高时,我似乎无法标记某些点(这是我对它为什么不起作用的假设),我也不能让框有一个坚实的背景,这样我就可以清楚地读取坐标(尝试将 alpha 设置为 1 但没有用)。这是一张这样的点如何显示远离实际位置并且文本被其后面的曲线挡住的图片。奇怪的是,当所有 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()

图右下角的股票代码似乎只显示基于最后一个使用的轴的值。在这里搜索后,我找到了 3 个解决方案,可以帮助显示一个包含点坐标的框:

  1. 将鼠标悬停在 matplotlib 中的某个点上时可以显示标签吗?(没有完全使用此代码,因为它用于散点图,但从这里找到了其他解决方案)
  2. 用鼠标悬停时使用 matplotlib 注释绘图线
  3. 将鼠标悬停在多轴上的一个点上时如何使标签出现?(用于将标签应用于具有多个轴的图形)

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

标签: pythonmatplotlibplothoverannotate

解决方案


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

解决方案不仅是更新注释的坐标,而且还要transform将点正确映射到以像素为单位的正确坐标。

注释的背景也是如此。由于您是在最底部的轴上创建它,因此注释位于这些轴中的线上方,但位于其他轴中的线下方。此处的解决方案是在最顶部的轴中创建注释。

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

推荐阅读