Matplotlib RegularPolygon集合在画布上的位置

4 投票
1 回答
5488 浏览
提问于 2025-04-18 06:56

我正在尝试用Python绘制一个特征图(自组织映射,简称SOM)。为了简单起见,可以想象成一个二维图,每个单元用六边形表示。

正如这个话题所展示的:Python中的六边形自组织映射,这些六边形是并排放置的,形成一个网格。

我写了以下这段代码,它在处理固定数量的多边形和少量形状(比如6 x 6或10 x 4的六边形)时运行得很好。不过,这种方法的一个重要特点是应该支持从3 x 3开始的任何网格形状。

def plot_map(grid,
             d_matrix,
             w=10,
             title='SOM Hit map'):
    """
    Plot hexagon map where each neuron is represented by a hexagon. The hexagon
    color is given by the distance between the neurons (D-Matrix) Scaled
    hexagons will appear on top of the background image whether the hits array
    is provided. They are scaled according to the number of hits on each
    neuron.

    Args:
    - grid: Grid dictionary (keys: centers, x, y ),
    - d_matrix: array contaning the distances between each neuron
    - w: width of the map in inches
    - title: map title

    Returns the Matplotlib SubAxis instance
    """
    n_centers = grid['centers']
    x, y = grid['x'], grid['y']
    fig = plt.figure(figsize=(1.05 * w,  0.85 * y * w / x), dpi=100)
    ax = fig.add_subplot(111)
    ax.axis('equal')
    # Discover difference between centers
    collection_bg = RegularPolyCollection(
        numsides=6,  # a hexagon
        rotation=0,
        sizes=(y * (1.3 * 2 * math.pi * w) ** 2 / x,),
        edgecolors = (0, 0, 0, 1),
        array= d_matrix,
        cmap = cm.gray,
        offsets = n_centers,
        transOffset = ax.transData,
    )
    ax.add_collection(collection_bg, autolim=True)
    ax.axis('off')
    ax.autoscale_view()
    ax.set_title(title)
    divider = make_axes_locatable(ax)
    cax = divider.append_axes("right", size="5%", pad=0.05)
    plt.colorbar(collection_bg, cax=cax)

    return ax

我尝试做一些可以自动识别网格形状的东西,但没有成功(我也不太确定为什么)。六边形之间总是会出现不想要的空隙。

6x6

3 x 3

11 x 3

总结一下:我希望能生成3x3、6x6或10x4(等等)这样的网格,使用六边形并且在给定点之间没有空隙,同时设置绘图的宽度。

根据要求,这里是六边形位置的数据。可以看到,它总是相同的模式。

3x3

  {'centers': array([[ 1.5       ,  0.8660254 ],
   [ 2.5       ,  0.8660254 ],
   [ 3.5       ,  0.8660254 ],
   [ 1.        ,  1.73205081],
   [ 2.        ,  1.73205081],
   [ 3.        ,  1.73205081],
   [ 1.5       ,  2.59807621],
   [ 2.5       ,  2.59807621],
   [ 3.5       ,  2.59807621]]),
  'x': array([ 3.]),
  'y': array([ 3.])}

6x6

{'centers': array([[ 1.5       ,  0.8660254 ],
   [ 2.5       ,  0.8660254 ],
   [ 3.5       ,  0.8660254 ],
   [ 4.5       ,  0.8660254 ],
   [ 5.5       ,  0.8660254 ],
   [ 6.5       ,  0.8660254 ],
   [ 1.        ,  1.73205081],
   [ 2.        ,  1.73205081],
   [ 3.        ,  1.73205081],
   [ 4.        ,  1.73205081],
   [ 5.        ,  1.73205081],
   [ 6.        ,  1.73205081],
   [ 1.5       ,  2.59807621],
   [ 2.5       ,  2.59807621],
   [ 3.5       ,  2.59807621],
   [ 4.5       ,  2.59807621],
   [ 5.5       ,  2.59807621],
   [ 6.5       ,  2.59807621],
   [ 1.        ,  3.46410162],
   [ 2.        ,  3.46410162],
   [ 3.        ,  3.46410162],
   [ 4.        ,  3.46410162],
   [ 5.        ,  3.46410162],
   [ 6.        ,  3.46410162],
   [ 1.5       ,  4.33012702],
   [ 2.5       ,  4.33012702],
   [ 3.5       ,  4.33012702],
   [ 4.5       ,  4.33012702],
   [ 5.5       ,  4.33012702],
   [ 6.5       ,  4.33012702],
   [ 1.        ,  5.19615242],
   [ 2.        ,  5.19615242],
   [ 3.        ,  5.19615242],
   [ 4.        ,  5.19615242],
   [ 5.        ,  5.19615242],
   [ 6.        ,  5.19615242]]),
'x': array([ 6.]),
'y': array([ 6.])}

11x4

  {'centers': array([[  1.5       ,   0.8660254 ],
   [  2.5       ,   0.8660254 ],
   [  3.5       ,   0.8660254 ],
   [  4.5       ,   0.8660254 ],
   [  5.5       ,   0.8660254 ],
   [  6.5       ,   0.8660254 ],
   [  7.5       ,   0.8660254 ],
   [  8.5       ,   0.8660254 ],
   [  9.5       ,   0.8660254 ],
   [ 10.5       ,   0.8660254 ],
   [ 11.5       ,   0.8660254 ],
   [  1.        ,   1.73205081],
   [  2.        ,   1.73205081],
   [  3.        ,   1.73205081],
   [  4.        ,   1.73205081],
   [  5.        ,   1.73205081],
   [  6.        ,   1.73205081],
   [  7.        ,   1.73205081],
   [  8.        ,   1.73205081],
   [  9.        ,   1.73205081],
   [ 10.        ,   1.73205081],
   [ 11.        ,   1.73205081],
   [  1.5       ,   2.59807621],
   [  2.5       ,   2.59807621],
   [  3.5       ,   2.59807621],
   [  4.5       ,   2.59807621],
   [  5.5       ,   2.59807621],
   [  6.5       ,   2.59807621],
   [  7.5       ,   2.59807621],
   [  8.5       ,   2.59807621],
   [  9.5       ,   2.59807621],
   [ 10.5       ,   2.59807621],
   [ 11.5       ,   2.59807621],
   [  1.        ,   3.46410162],
   [  2.        ,   3.46410162],
   [  3.        ,   3.46410162],
   [  4.        ,   3.46410162],
   [  5.        ,   3.46410162],
   [  6.        ,   3.46410162],
   [  7.        ,   3.46410162],
   [  8.        ,   3.46410162],
   [  9.        ,   3.46410162],
   [ 10.        ,   3.46410162],
   [ 11.        ,   3.46410162]]),
  'x': array([ 11.]),
  'y': array([ 4.])}

1 个回答

5

我找到了一种解决办法,就是根据给定的dpi(每英寸点数)来计算图形的大小(以英寸为单位)。然后,我通过画一个隐藏的散点图来计算两个相邻点之间的像素距离。这样,我就能计算出六边形的内切圆半径,并正确估算六边形内部圆的大小(这正是matplotlib所需要的)。

最后没有出现任何空隙!

import matplotlib.pyplot as plt
from matplotlib import colors, cm
from matplotlib.collections import RegularPolyCollection
from mpl_toolkits.axes_grid1 import make_axes_locatable
import math
import numpy as np

def plot_map(grid,
             d_matrix,
             w=1080,
            dpi=72.,
            title='SOM Hit map'):
    """
    Plot hexagon map where each neuron is represented by a hexagon. The hexagon
    color is given by the distance between the neurons (D-Matrix)

    Args:
    - grid: Grid dictionary (keys: centers, x, y ),
    - d_matrix: array contaning the distances between each neuron
    - w: width of the map in inches
    - title: map title

    Returns the Matplotlib SubAxis instance
    """
    n_centers = grid['centers']
    x, y = grid['x'], grid['y']
    # Size of figure in inches
    xinch = (x * w / y) / dpi
    yinch = (y * w / x) / dpi
    fig = plt.figure(figsize=(xinch, yinch), dpi=dpi)
    ax = fig.add_subplot(111, aspect='equal')
    # Get pixel size between to data points
    xpoints = n_centers[:, 0]
    ypoints = n_centers[:, 1]
    ax.scatter(xpoints, ypoints, s=0.0, marker='s')
    ax.axis([min(xpoints)-1., max(xpoints)+1.,
             min(ypoints)-1., max(ypoints)+1.])
    xy_pixels = ax.transData.transform(np.vstack([xpoints, ypoints]).T)
    xpix, ypix = xy_pixels.T

    # In matplotlib, 0,0 is the lower left corner, whereas it's usually the
    # upper right for most image software, so we'll flip the y-coords
    width, height = fig.canvas.get_width_height()
    ypix = height - ypix

    # discover radius and hexagon
    apothem = .9 * (xpix[1] - xpix[0]) / math.sqrt(3)
    area_inner_circle = math.pi * (apothem ** 2)
    collection_bg = RegularPolyCollection(
        numsides=6,  # a hexagon
        rotation=0,
        sizes=(area_inner_circle,),
        edgecolors = (0, 0, 0, 1),
        array= d_matrix,
        cmap = cm.gray,
        offsets = n_centers,
        transOffset = ax.transData,
    )
    ax.add_collection(collection_bg, autolim=True)

    ax.axis('off')
    ax.autoscale_view()
    ax.set_title(title)
    divider = make_axes_locatable(ax)
    cax = divider.append_axes("right", size="10%", pad=0.05)
    plt.colorbar(collection_bg, cax=cax)

    return ax

撰写回答