Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Matplotlib RegularPolygon collection location on the canvas

I am trying to plot a feature map (SOM) using python. To keep it simple, imagine a 2D plot where each unit is represented as an hexagon.

As it is shown on this topic: Hexagonal Self-Organizing map in Python the hexagons are located side-by-side formated as a grid.

I manage to write the following piece of code and it works perfectly for a set number of polygons and for only few shapes (6 x 6 or 10 x 4 hexagons for example). However one important feature of a method like this is to support any grid shape from 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

I've tried to make something that automatically understands the grid shape. It didn't work (and I'm not sure why). It always appear a undesired space between the hexagons

6x6

3 x 3

11 x 3

Summarising: I would like to generate 3x3 or 6x6 or 10x4 (and so on) grid using hexagons with no spaces in the between for given points and setting the plot width.

As it was asked, here is the data for the hexagons location. As you can see, it always the same pattern

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.])}
like image 803
Fernando Ferreira Avatar asked Oct 23 '25 14:10

Fernando Ferreira


1 Answers

I've manage to find a workaround by calculating the figure size of inches according the given dpi. After, I compute the pixel distance between two adjacent points (by plotting it using a hidden scatter plot). This way I could calculate the hexagon apothem and estimate correctly the size of the hexagon's inner circle (as the matplotlib expects).

No gaps in the end!

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
like image 58
Fernando Ferreira Avatar answered Oct 26 '25 04:10

Fernando Ferreira



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!