Python Matplotlib 重叠注释/文本

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/19073683/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-19 12:44:59  来源:igfitidea点击:

Matplotlib overlapping annotations / text

pythonmatplotlibannotate

提问by homebrand

I'm trying to stop annotation text overlapping in my graphs. The method suggested in the accepted answer to Matplotlib overlapping annotationslooks extremely promising, however is for bar graphs. I'm having trouble converting the "axis" methods over to what I want to do, and I don't understand how the text lines up.

我试图阻止注释文本在我的图表中重叠。Matplotlib 重叠注释的公认答案中建议的方法看起来非常有希望,但适用于条形图。我在将“轴”方法转换为我想要做的事情时遇到了麻烦,而且我不明白文本是如何排列的。

import sys
import matplotlib.pyplot as plt


# start new plot
plt.clf()
plt.xlabel("Proportional Euclidean Distance")
plt.ylabel("Percentage Timewindows Attended")
plt.title("Test plot")

together = [(0, 1.0, 0.4), (25, 1.0127692669427917, 0.41), (50, 1.016404709797609, 0.41), (75, 1.1043426359673716, 0.42), (100, 1.1610446924342996, 0.44), (125, 1.1685687930691457, 0.43), (150, 1.3486407784550272, 0.45), (250, 1.4013999168008104, 0.45)]
together.sort()

for x,y,z in together:
    plt.annotate(str(x), xy=(y, z), size=8)

eucs = [y for (x,y,z) in together]
covers = [z for (x,y,z) in together]

p1 = plt.plot(eucs,covers,color="black", alpha=0.5)

plt.savefig("test.png")

Images (if this works) can be found here(this code):

可以在此处找到图像(如果可行)(此代码):

image1

图片1

and here(more complicated):

这里(更复杂):

image2

图像2

采纳答案by Phlya

I just wanted to post here another solution, a small library I wrote to implement this kind of things: https://github.com/Phlya/adjustTextAn example of the process can be seen here: enter image description here

我只是想在这里发布另一个解决方案,我写的一个小库来实现这种事情:https: //github.com/Phlya/adjustText可以在此处看到该过程的示例: enter image description here

Here is the example image:

这是示例图像:

import matplotlib.pyplot as plt
from adjustText import adjust_text
import numpy as np
together = [(0, 1.0, 0.4), (25, 1.0127692669427917, 0.41), (50, 1.016404709797609, 0.41), (75, 1.1043426359673716, 0.42), (100, 1.1610446924342996, 0.44), (125, 1.1685687930691457, 0.43), (150, 1.3486407784550272, 0.45), (250, 1.4013999168008104, 0.45)]
together.sort()

text = [x for (x,y,z) in together]
eucs = [y for (x,y,z) in together]
covers = [z for (x,y,z) in together]

p1 = plt.plot(eucs,covers,color="black", alpha=0.5)
texts = []
for x, y, s in zip(eucs, covers, text):
    texts.append(plt.text(x, y, s))

plt.xlabel("Proportional Euclidean Distance")
plt.ylabel("Percentage Timewindows Attended")
plt.title("Test plot")
adjust_text(texts, only_move='y', arrowprops=dict(arrowstyle="->", color='r', lw=0.5))
plt.show()

enter image description here

enter image description here

If you want a perfect figure, you can fiddle around a little. First, let's also make text repel the lines - for that we just create lots of virtual points along them using scipy.interpolate.interp1d.

如果你想要一个完美的身材,你可以稍微摆弄一下。首先,让我们也让文本排斥线条——为此,我们只是使用 scipy.interpolate.interp1d 沿着它们创建了许多虚拟点。

We want to avoid moving the labels along the x-axis, because, well, why not do it for illustrative purposes. For that we use the parameter only_move={'points':'y', 'text':'y'}. If we want to move them along x axis only in the case that they are overlapping with text, use move_only={'points':'y', 'text':'xy'}. Also in the beginning the function chooses optimal alignment of texts relative to their original points, so we only want that to happen along the y axis too, hence autoalign='y'. We also reduce the repelling force from points to avoid text flying too far away due to our artificial avoidance of lines. All together:

我们希望避免沿 x 轴移动标签,因为,为什么不出于说明目的而这样做。为此,我们使用参数only_move={'points':'y', 'text':'y'}。如果我们只想在它们与文本重叠的情况下沿 x 轴移动它们,请使用move_only={'points':'y', 'text':'xy'}. 同样在开始时,函数选择文本相对于其原始点的最佳对齐方式,因此我们只希望沿 y 轴也发生这种情况,因此autoalign='y'。我们还减少了点的排斥力,以避免由于我们人为地避免线条而使文本飞得太远。全部一起:

from scipy import interpolate
p1 = plt.plot(eucs,covers,color="black", alpha=0.5)
texts = []
for x, y, s in zip(eucs, covers, text):
    texts.append(plt.text(x, y, s))

f = interpolate.interp1d(eucs, covers)
x = np.arange(min(eucs), max(eucs), 0.0005)
y = f(x)    

plt.xlabel("Proportional Euclidean Distance")
plt.ylabel("Percentage Timewindows Attended")
plt.title("Test plot")
adjust_text(texts, x=x, y=y, autoalign='y',
            only_move={'points':'y', 'text':'y'}, force_points=0.15,
            arrowprops=dict(arrowstyle="->", color='r', lw=0.5))
plt.show()

enter image description here

enter image description here

回答by homebrand

With a lot of fiddling, I figured it out. Again credit for the original solution goes to the answer for Matplotlib overlapping annotations.

经过一番折腾,我想通了。原始解决方案的功劳再次归功于Matplotlib 重叠注释的答案。

I don't however know how to find the exact width and height of the text. If someone knows, please post an improvement (or add a comment with the method).

但是,我不知道如何找到文本的确切宽度和高度。如果有人知道,请发表改进意见(或对方法添加评论)。

import sys
import matplotlib
import matplotlib.pyplot as plt
import numpy as np

def get_text_positions(text, x_data, y_data, txt_width, txt_height):
    a = zip(y_data, x_data)
    text_positions = list(y_data)
    for index, (y, x) in enumerate(a):
        local_text_positions = [i for i in a if i[0] > (y - txt_height) 
                            and (abs(i[1] - x) < txt_width * 2) and i != (y,x)]
        if local_text_positions:
            sorted_ltp = sorted(local_text_positions)
            if abs(sorted_ltp[0][0] - y) < txt_height: #True == collision
                differ = np.diff(sorted_ltp, axis=0)
                a[index] = (sorted_ltp[-1][0] + txt_height, a[index][1])
                text_positions[index] = sorted_ltp[-1][0] + txt_height*1.01
                for k, (j, m) in enumerate(differ):
                    #j is the vertical distance between words
                    if j > txt_height * 2: #if True then room to fit a word in
                        a[index] = (sorted_ltp[k][0] + txt_height, a[index][1])
                        text_positions[index] = sorted_ltp[k][0] + txt_height
                        break
    return text_positions

def text_plotter(text, x_data, y_data, text_positions, txt_width,txt_height):
    for z,x,y,t in zip(text, x_data, y_data, text_positions):
        plt.annotate(str(z), xy=(x-txt_width/2, t), size=12)
        if y != t:
            plt.arrow(x, t,0,y-t, color='red',alpha=0.3, width=txt_width*0.1, 
                head_width=txt_width, head_length=txt_height*0.5, 
                zorder=0,length_includes_head=True)

# start new plot
plt.clf()
plt.xlabel("Proportional Euclidean Distance")
plt.ylabel("Percentage Timewindows Attended")
plt.title("Test plot")

together = [(0, 1.0, 0.4), (25, 1.0127692669427917, 0.41), (50, 1.016404709797609, 0.41), (75, 1.1043426359673716, 0.42), (100, 1.1610446924342996, 0.44), (125, 1.1685687930691457, 0.43), (150, 1.3486407784550272, 0.45), (250, 1.4013999168008104, 0.45)]
together.sort()

text = [x for (x,y,z) in together]
eucs = [y for (x,y,z) in together]
covers = [z for (x,y,z) in together]

p1 = plt.plot(eucs,covers,color="black", alpha=0.5)

txt_height = 0.0037*(plt.ylim()[1] - plt.ylim()[0])
txt_width = 0.018*(plt.xlim()[1] - plt.xlim()[0])

text_positions = get_text_positions(text, eucs, covers, txt_width, txt_height)

text_plotter(text, eucs, covers, text_positions, txt_width, txt_height)

plt.savefig("test.png")
plt.show()

Creates http://i.stack.imgur.com/xiTeU.pngenter image description here

创建http://i.stack.imgur.com/xiTeU.pngenter image description here

The more complicated graph is now http://i.stack.imgur.com/KJeYW.png, still a bit iffy but much better! enter image description here

更复杂的图现在是http://i.stack.imgur.com/KJeYW.png,仍然有点不确定但好多了! enter image description here

回答by Thomas G.

Easy solution here:(for jupyter notebooks)

这里的简单解决方案:(适用于 jupyter 笔记本)

%matplotlib notebook
import mplcursors

plt.plot.scatter(y=YOUR_Y_DATA, x =YOUR_X_DATA)


mplcursors.cursor(multiple = True).connect(
    "add", lambda sel: sel.annotation.set_text(
          YOUR_ANOTATION_LIST[sel.target.index]
))

Right click on a dot to showits anotation.

右键单击一个点以显示其注释。

Left click on an anotation to close it.

左键单击注释将其关闭

Right click and drag on an anotation to move it.

右键单击并拖动注释以移动它

enter image description here

enter image description here