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
Matplotlib overlapping annotations / text
提问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):
可以在此处找到图像(如果可行)(此代码):
and here(more complicated):
和这里(更复杂):
采纳答案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:
我只是想在这里发布另一个解决方案,我写的一个小库来实现这种事情:https: //github.com/Phlya/adjustText可以在此处看到该过程的示例:
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()
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()
回答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.png
创建http://i.stack.imgur.com/xiTeU.png
The more complicated graph is now http://i.stack.imgur.com/KJeYW.png, still a bit iffy but much better!
更复杂的图现在是http://i.stack.imgur.com/KJeYW.png,仍然有点不确定但好多了!
回答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.
右键单击并拖动注释以移动它。