我怎样才能在凸包外面(整齐地)得到注释?

时间:2017-11-07 10:35:57

标签: python python-3.x annotations 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

1 个答案:

答案 0 :(得分:0)

首先,让我们看一下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()

Regular Polygon with annotations

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

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

    (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)
    
  3. 再次完整代码&amp;结果如下:

    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()
    

    Polygon with annotations #2