matplotlib 保留地物上的数据点注解

tjjdgumg  于 2023-04-21  发布在  其他
关注(0)|答案(2)|浏览(147)

我有一个pyplot图,其中每个数据点都标有一个指向数据点的箭头,我将它格式化,使数据与它注解的数据点有一个恒定的偏移量。这很好,直到我用一个数据点太接近图的边缘,注解被切断。我希望有一种方法可以格式化我的注解,这样它就可以自动将自己定位在我的数据之外,但仍然留在我的图中。下面是我如何格式化我的注解的一个片段。

for label, x, y in zip(bagOlabels, time, height):
    ax.annotate(
                label,
                xy = (x,y), xytext = (50,-20),
                textcoords = 'offset points', ha = 'right', va = 'top',
                arrowprops = dict(arrowstyle = '->', 
                connectionstyle = 'arc3,rad=0')
                )

xytext = (50,-20)是我设置标签偏移量的地方。我一直在做挖掘,但我还没有找到我要找的东西。如果你有任何关于如何实现这一点的见解,我很乐意听到它。
谢谢。

sdnqo3pr

sdnqo3pr1#

有一些策略你可以尝试:
1.如果该点位于图的右半部分,则使用负偏移放置注解:

for label, x, y in zip(bagOlabels, time, height):
    offset = (50, 20)
    if x > mid_x:
       offset = (-50, 20)  # (-50, -20) could be better

ax.annotate(...)

1.放大图,使所有注解都适合该图。
在第一种情况下,注解可能彼此重叠。

更新

方法get_xlim返回图在x轴上的极限:

x = range(0, 100)
y = map(lambda x: x*x, x)

fig = pyplot.figure()
ax1 = fig.add_subplot(1, 1, 1)
ax1.plot(x, y, 'r.', x, y, 'r-')
xlim = ax1.get_xlim()
8fsztsew

8fsztsew2#

我找到了一种方法,虽然很粗糙,它包括首先在正常位置绘制注解,重新绘制画布,然后如果它在轴边界之外,则删除它并绘制一个新的注解。
我不知道如何更新原始注解的位置。这需要与this question相同的答案。
需要画布重绘fig.canvas.draw()来强制注解更新它的bbox,因此它可以与轴边界进行比较。我还没有找到一种方法来更新bbox而不进行完整的画布重绘。(这需要在代码中进行更多的挖掘)
画布重绘并在新位置重新创建注解会导致交互式图形上出现可见的 Flink ,但在保存到文件时应该可以正常工作,其中只有最终位置可见。
示例伪代码:(从工作代码复制的关键部分)

pos_dc = (X_DC, Y_DC) # position where the arrow is pointing, in data coordinates

xytext = [20, 20] # normal x and y position of the annotation textbox, in points coordines
xytext_flip = [-80, -80] # flipped x and y positions, when it's too close to edge, in points coordines
TEXT = ("Text box contents" + "\n" +
        "with multiple" + "\n" +
        "lines"
        )

# create original annotation to measure its position
annot = ax.annotate ("", xy=(0,0), xytext=(xytext[0],xytext[1]),
            textcoords="offset points", bbox=dict(boxstyle="round",
            fc="w"), arrowprops=dict(arrowstyle="->", color='black')
        )
annot.xy = pos_dc
annot.set_text (TEXT)

# draw the canvas, forcing the annotation position to be updated
# fig.canvas.draw_idle() didn't work here
fig.canvas.draw()

# measure the annotation's position
p = annot.get_bbox_patch()
ex = p.get_extents() # annotation textbox, in pixel coordinates
T = ax.transData.inverted()
ex_datac = T.transform (ex) # annotation textbox, in data coordinates

# ex_datac structure in data coordinates:
# [[xleft  ybottom] [xright  ytop]]

ax_xb = ax.get_xbound()
ax_yb = ax.get_ybound() # axis bounds (the visible area), in data coordinates

# structure in data coordinates:
# ax_xb = (xleft, xright) , ax_yb = (ybottom, ytop)

# in data coordinates, (y) top is a GREATER value than bottom, use '>' to compare it
dc_top_of_display = ax_yb[1]
dc_bottom_of_display = ax_yb[0]
dc_left_of_display = ax_xb[0]
dc_right_of_display = ax_xb[1]

# only testing the right and top edges of the annotation textbox, which was sufficient in this case
dc_box_right = ex_datac[1][0]
dc_box_top = ex_datac[1][1]

# test whether the annotation textbox is outside of the axis bounds
flip_x = dc_box_right > dc_right_of_display
flip_y = dc_box_top > dc_top_of_display # top is GREATER than bottom, use '>'

# if the text box right or top edges are outside of the axis bounds, update the annotation's position
if flip_x or flip_y:
    xytext2 = [0, 0]
    xytext2[0] = xytext[0] if not flip_x else xytext_flip[0]
    xytext2[1] = xytext[1] if not flip_y else xytext_flip[1]

    # remove the original annotation
    annot.remove ()

    # create new annotation with updated position
    annot = ax.annotate ("", xy=(0,0), xytext=(xytext2[0],xytext2[1]),
                textcoords="offset points", bbox=dict(boxstyle="round",
                fc="w"), arrowprops=dict(arrowstyle="->", color='black')
            )
    annot.xy = pos_dc
    annot.set_text (TEXT)

    # redraw the canvas
    fig.canvas.draw_idle()

相关问题