Matplotlib: figure.add_axes convert from x,y to relative coordinates

Viewed 640

I am working on a self-organizing map that I want to show as a hexagonal lattice with spider charts representing the weight vector/neuron in the corresponding cells and have been able to create a hexagonal grid via the following:

def hex_plot(ws,a=1):
    """
     plots a m (rows) x n (cols) hexagonal grid with offset rows where a is 
     the hexagon's side length and m and n are determined by the first two 
     dimensions of the weight vectors ws
    """
    m,n,_ = ws.shape
    offsety = .75 * 2*a
    offsetx = numpy.sqrt(3) * a
    oddrow = numpy.sqrt(3)/2 * a
    x,y = 0,0

    fig,ax = plt.subplots(figsize=(7.5,7.5))
    ax.set_aspect('equal')
    ax.set_zorder(1)

    cs = {}
    for i in range(m):
        for j in range(n):
            offsetr = oddrow if i % 2 != 0 else 0
            cp = (j*offsetx+offsetr,-i*offsety)
            cs[(i,j)] = cp
            hexg = RegularPolygon(cp,numVertices=6,radius=a,facecolor='white',edgecolor='k')
            ax.add_patch(hexg)
            ax.text(cp[0], cp[1], "{},{}\n{:.2f},{:.2f}".format(i,j,cp[0],cp[1]), ha='center', va='center', size=10)
    xy = [cs[(i,j)] for i in range(m) for j in range(n)]    
    ax.scatter([x for x,_ in xy],[y for _,y in xy],c='white',alpha=0.5)
    ax.set_xticks([])
    ax.set_yticks([])
    plt.show()

This gives me the following:
5x4 blank hexagon grid

With that I am trying to place spider charts of the neurons 5x4 trained SOM representing each weight vector such that the spider chart at 0,0 (top left) is inside the hexagonal cell at 0,0 (top left). I tried using sub plots but the difficulty arises in the spider charts having a polar projections whereas the grids do not. My second attempt involves using "add_axes". By adding the following immediately prior to plt.show() in the code above:

w = ws[0][0] # current weight vector
a = numpy.linspace(0,2*numpy.pi,len(w),endpoint=False)
a = numpy.concatenate((a,[a[0]]))
w = numpy.concatenate((w,[w[0]]))

ax1 = fig.add_axes([0.0,0.0,0.1,0.1],polar=True,zorder=2)

ax1.set_thetagrids([])    
ax1.fill(a,w,alpha=1.0)
ax1.set_yticklabels([])
ax1.set_rticks([])

I can place a spider chart in the lower left canvas: Spider chart over hex, But since the rectangle specified in add_axes is in coordinates relative to the figure, I cannot figure out to determine what coordinate 0,0 in the orginal axes (hexagon 0,0) would map to in relative coordinates. I looked at transformations but it wasn't helpful.

Has anyone ran into this and come up with a solution? Thanks

1 Answers

I see two options here:

Plot all shapes in a single axes

Since it does not look like there is a need for a special polar axes you could just plot the fills all in the same axes at the respective positions of the hexagons.

import numpy as np
import matplotlib.pyplot as plt
from matplotlib.patches import RegularPolygon


def hex_plot(ws,a=1):
    """
     plots a m (rows) x n (cols) hexagonal grid with offset rows where a is 
     the hexagon's side length and m and n are determined by the first two 
     dimensions of the weight vectors ws
    """
    m,n,l = ws.shape
    offsety = .75 * 2*a
    offsetx = np.sqrt(3) * a
    oddrow = np.sqrt(3)/2 * a
    theta = np.linspace(0, 2*np.pi, l+1)

    fig,ax = plt.subplots(figsize=(7.5,7.5))
    ax.set_aspect('equal')
    ax.set_zorder(1)

    cs = {}
    for i in range(m):
        for j in range(n):
            offsetr = oddrow if i % 2 != 0 else 0
            cp = (j*offsetx+offsetr,-i*offsety)
            cs[(i,j)] = cp
            hexg = RegularPolygon(cp,numVertices=6,radius=a,
                                  facecolor='white',edgecolor='k')
            ax.add_patch(hexg)
            ax.text(cp[0], cp[1], "{},{}\n{:.2f},{:.2f}".format(i,j,cp[0],cp[1]), 
                    ha='center', va='center', size=10)

            r = ws[i,j,:]
            r = np.concatenate((r, [r[0]]))
            x,y = (np.c_[r*np.sin(theta), r*np.cos(theta)] + cp).T
            ax.fill(x,y, color="C0")


    ax.autoscale()
    ax.set_xticks([])
    ax.set_yticks([])
    plt.show()


hex_plot(np.random.rand(5,4,6),a=1)

enter image description here

Use inset_axes

If you really need the axes, e.g. to show the grids, you may position inset_axes at the respective positions.

import numpy as np
import matplotlib.pyplot as plt
from matplotlib.patches import RegularPolygon
from mpl_toolkits.axes_grid1.inset_locator import inset_axes
from matplotlib.projections import get_projection_class

def hex_plot(ws,a=1):
    """
     plots a m (rows) x n (cols) hexagonal grid with offset rows where a is 
     the hexagon's side length and m and n are determined by the first two 
     dimensions of the weight vectors ws
    """
    m,n,l = ws.shape
    offsety = .75 * 2*a
    offsetx = np.sqrt(3) * a
    oddrow = np.sqrt(3)/2 * a
    theta = np.linspace(0, 2*np.pi, l+1)


    fig,ax = plt.subplots(figsize=(7.5,7.5))
    ax.set_aspect('equal')
    ax.set_zorder(1)

    cs = {}
    axcs = {}
    for i in range(m):
        for j in range(n):
            offsetr = oddrow if i % 2 != 0 else 0
            cp = (j*offsetx+offsetr,-i*offsety)
            cs[(i,j)] = cp
            hexg = RegularPolygon(cp,numVertices=6,radius=a,
                                  facecolor='white',edgecolor='k')
            ax.add_patch(hexg)
            ax.text(cp[0], cp[1], "{},{}\n{:.2f},{:.2f}".format(i,j,cp[0],cp[1]), 
                    ha='center', va='center', size=10)

            axins=inset_axes(ax, width="100%", height="100%", 
                             bbox_to_anchor=(cp[0]-offsetx/2, cp[1]-offsety/2, offsetx, offsety),
                             bbox_transform=ax.transData, borderpad=0, 
                             axes_class=get_projection_class("polar"),
                             )
            axins.set_zorder(4)
            axcs[(i,j)] = axins

            r = ws[i,j,:]
            r = np.concatenate((r, [r[0]]))
            axins.fill(theta,r)

            axins.set_yticklabels([])
            axins.set_xticklabels([])


    ax.autoscale()
    ax.set_xticks([])
    ax.set_yticks([])
    plt.show()


hex_plot(np.random.rand(5,4,6),a=1)

enter image description here

Related