【问题标题】:Making labels appear while hovering over plot for graphs *with high y-axis values*, multiple lines and multiple axes将标签悬停在图表上时显示标签*具有高 y 轴值*、多条线和多轴
【发布时间】:2020-11-09 18:35:51
【问题描述】:

我想从我使用 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. Annotate lines of a plot with matplotlib when hover with mouse [duplicate](用于将之前的解决方案应用于线条/曲线)
  3. How to make labels appear when hovering over a point in multiple axis?(用于将标签应用于具有多个轴的图形)

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

【问题讨论】:

    标签: python matplotlib plot hover annotate


    【解决方案1】:

    本质上,您的问题是您将注释创建为属于轴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()
    

    【讨论】:

    • 很好的答案,但是如果 X 轴不是数字它的日期时间在 text = f'{line.get_label()} 中有这个问题,我怎么能改变: ({posx:.2f},{posy: .2f})' 谢谢
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2022-11-10
    • 1970-01-01
    • 1970-01-01
    • 2014-12-07
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多