这个问题是this的后续问题。我如何布置注释,以便在标记点完全或几乎重合时它们仍然可读?我需要一个程序化的解决方案,手动调整偏移量不是一个选项。带丑陋标签的样本:
import numpy as np
import matplotlib.pyplot as plt
np.random.seed(0)
N = 10
data = np.random.random((N, 4))
data[1, :2] = data[0, :2]
data[-1, :2] = data[-2, :2] + .01
labels = ['point{0}'.format(i) for i in range(N)]
plt.subplots_adjust(bottom = 0.1)
plt.scatter(
data[:, 0], data[:, 1], marker = 'o', c = data[:, 2], s = data[:, 3]*1500,
cmap = plt.get_cmap('Spectral'))
for label, x, y in zip(labels, data[:, 0], data[:, 1]):
plt.annotate(
label,
xy = (x, y), xytext = (-20, 20),
textcoords = 'offset points', ha = 'right', va = 'bottom',
bbox = dict(boxstyle = 'round,pad=0.5', fc = 'yellow', alpha = 0.5),
arrowprops = dict(arrowstyle = '->', connectionstyle = 'arc3,rad=0'))
plt.show()
答案 0 :(得分:1)
取最后一个点之间的距离,设置阈值保持,然后相应地翻转x,y文本。见下文。
import numpy as np
import matplotlib.pyplot as plt
np.random.seed(0)
N = 10
data = np.random.random((N, 4))
data[1, :2] = data[0, :2]
data[-1, :2] = data[-2, :2] + .01
labels = ['point{0}'.format(i) for i in range(N)]
plt.subplots_adjust(bottom = 0.1)
plt.scatter(
data[:, 0], data[:, 1], marker = 'o', c = data[:, 2], s = data[:, 3]*1500,
cmap = plt.get_cmap('Spectral'))
old_x = old_y = 1e9 # make an impossibly large initial offset
thresh = .1 #make a distance threshold
for label, x, y in zip(labels, data[:, 0], data[:, 1]):
#calculate distance
d = ((x-old_x)**2+(y-old_y)**2)**(.5)
#if distance less than thresh then flip the arrow
flip = 1
if d < .1: flip=-2
plt.annotate(
label,
xy = (x, y), xytext = (-20*flip, 20*flip),
textcoords = 'offset points', ha = 'right', va = 'bottom',
bbox = dict(boxstyle = 'round,pad=0.5', fc = 'yellow', alpha = 0.5),
arrowprops = dict(arrowstyle = '->', connectionstyle = 'arc3,rad=0'))
old_x = x
old_y = y
plt.show()
导致:
答案 1 :(得分:0)
这是我最终的结果。对于所有情况都不是完美的,对于这个示例问题,它甚至不能顺利运行,但我认为它足以满足我的需求。感谢Dan的回答,指出了我正确的方向。
import numpy as np
import matplotlib.pyplot as plt
from scipy.spatial import cKDTree
def get_label_xy(tree, thresh, data, i):
neighbors = tree.query_ball_point([data[i, 0], data[i, 1]], thresh)
if len(neighbors) == 1:
xy = (-30, 30)
else:
mean = np.mean(data[:, :2][neighbors], axis=0)
if mean[0] == data[i, 0] and mean[1] == data[i, 1]:
if i < np.max(neighbors):
xy = (-30, 30)
else:
xy = (30, -30)
else:
angle = np.arctan2(data[i, 1] - mean[1], data[i, 0] - mean[0])
if angle > np.pi / 2:
xy = (-30, 30)
elif angle > 0:
xy = (30, 30)
elif angle > -np.pi / 2:
xy = (30, -30)
else:
xy = (-30, -30)
return xy
def labeled_scatter_plot(data, labels):
plt.subplots_adjust(bottom = 0.1)
plt.scatter(
data[:, 0], data[:, 1], marker = 'o', c = data[:, 2], s = data[:, 3]*1500,
cmap = plt.get_cmap('Spectral'))
tree = cKDTree(data[:, :2])
thresh = .1
for i in range(data.shape[0]):
xy = get_label_xy(tree, thresh, data, i)
plt.annotate(
labels[i],
xy = data[i, :2], xytext = xy,
textcoords = 'offset points', ha = 'center', va = 'center',
bbox = dict(boxstyle = 'round,pad=0.5', fc = 'yellow', alpha = 0.5),
arrowprops = dict(arrowstyle = '->', connectionstyle = 'arc3,rad=0'))
np.random.seed(0)
N = 10
data = np.random.random((N, 4))
data[1, :2] = data[0, :2]
data[-1, :2] = data[-2, :2] + .01
data[5, :2] = data[4, :2] + [.05, 0]
data[6, :2] = data[4, :2] + [.05, .05]
data[7, :2] = data[4, :2] + [0, .05]
labels = ['point{0}'.format(i) for i in range(N)]
labeled_scatter_plot(data, labels)
plt.show()