如何在凸包外(整齐地)获得注释?

How can I get annotations (neatly) outside the convex hull?

我开发了一些代码来自动生成等边 n 维多边形:

# Create equilateral n-dimensional polygon

def polygon(side, radius=1, rotation=0, translation=None):
    import math
    vertex = 2 * math.pi / side

    points = [
        (math.sin(vertex * i + rotation) * radius,
         math.cos(vertex * i + rotation) * radius)
         for i in range(side)]

    if translation:
       points = [[sum(pair) for pair in zip(point, translation)]
                  for point in points]
return np.array(points)

现在,我想在这个 n 维多边形的外侧 角上整齐地放置标签。在下面的示例中,我创建了一个半径为 10 的六边形,以 (3,3) 为中心。

import matplotlib.pyplot as plt

pol = polygon(7, 10, 0, [3,3])
hull = ConvexHull(pol)
labels = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', "L", 'M', 
          'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z']

fig = plt.figure(figsize=(4, 4), dpi=100)
for simplex in hull.simplices:
    plt.plot(pol[simplex,0], pol[simplex,1], 'k-')
plt.plot(pol[:,0], pol[:,1], 'gs', ms=10)
if labels is not None:
    for i, label in enumerate(labels):
        if i <= len(pol)-1:
            plt.annotate(label, xy=(pol[:,0][i],pol[:,1][i]), xytext=(0, 8), 
textcoords='offset points', ha="center", va="bottom")
plt.axis('off')
plt.show()

遗憾的是,如图所示,只有A点、B点、F点整齐地位于六边形之外。是否有系统的方法将标签注释到多边形(在本例中为六边形)的外角,无论尺寸 n?提前致谢!

Plot of hexagon with wrongly placed annotations

首先我们来看n维正多边形的特例

为此,您可以将注释放在稍微大一点的多边形的顶点上(我使用了原始半径的 1.2 倍)。

下面是完整的代码和结果。

import matplotlib.pyplot as plt
from scipy.spatial import ConvexHull

r = 10  # radius
center = [3, 3]
pol = polygon(7, r, 0, center)
pol2 = polygon(7, 1.2*r, 0, center)  # for annotations
hull = ConvexHull(pol)
labels = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', "L", 'M', 
          'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z']

fig = plt.figure(figsize=(4, 4), dpi=100)
for simplex in hull.simplices:
    plt.plot(pol[simplex,0], pol[simplex,1], 'k-')
plt.plot(pol[:,0], pol[:,1], 'gs', ms=10)
if labels is not None:
    for i, label in enumerate(labels):
        if i <= len(pol)-1:
            plt.annotate(label, xy=(pol2[i,0], pol2[i,1]), xytext=(0, 0), 
                         textcoords='offset points', ha="center", va="center")
plt.xlim(center[0] - 1.5*r, center[0] + 1.5*r)
plt.ylim(center[1] - 1.5*r, center[1] + 1.5*r)
plt.axis('off')
plt.show()

现在,让我们看一下一般的凸包。一个简单的解决方案如下:

  1. 对于每个单纯形S,计算其相邻两个单纯形(称为N_1和N_2)的中点M。我们知道这个中点一定在凸包的内部。

    (N_1, N_2) = hull.neighbors(S)
    M = (pol[N_1] + pol[N_2]) / 2
    
  2. 从M到S画直线,取直线上的新点M_ext,使S与M和M_ext等距,但M_ext 在另一边。我们知道 M_ext 肯定是,在那种情况下。

    M_ext = pol[S] + (pol[S] - M)
    

    您可以对其进行归一化,以便注释与单纯形的距离相同(例如使用 numpy.linalg.norm)。在我的代码中,我还乘以一个常数因子,这样文本就不会与顶点重叠。

    M_ext = pol[S] + (pol[S] - M) / np.linalg.norm(pol[S]-M)
    

完整代码和结果如下:

import matplotlib.pyplot as plt
import numpy as np
from scipy.spatial import ConvexHull

r = 10  # radius
center = [3, 3]
pol = polygon(7, r, 0, center)
pol2 = polygon(7, 1.2*r, 0, center)  # for annotations
hull = ConvexHull(pol)
labels = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', "L", 'M', 
          'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z']

fig = plt.figure(figsize=(4, 4), dpi=100)
for simplex in hull.simplices:
    plt.plot(pol[simplex,0], pol[simplex,1], 'k-')
plt.plot(pol[:,0], pol[:,1], 'gs', ms=10)
if labels is not None:
    for i, label in enumerate(labels):
        if i <= len(pol)-1:
            S = i
            (N_1, N_2) = hull.neighbors[S]
            M = (pol[N_1] + pol[N_2]) / 2
            M_ext = pol[S] + (pol[S] - M) / np.linalg.norm(pol[S] - M) * 0.2*r
            plt.annotate(label, xy=M_ext, xytext=(0, 0), 
textcoords='offset points', ha="center", va="center")
plt.xlim(center[0] - 1.5*r, center[0] + 1.5*r)
plt.ylim(center[1] - 1.5*r, center[1] + 1.5*r)
plt.axis('off')
plt.show()