Displaying data in a hexagonal grid using Python
Asked Answered
S

3

6

What I am looking for is a method or class that allows me to display a list of hexagons in a grid. Ideally I would then be able to use some form of set-method to change the color/hatching/border of the individual hexagons.

The hexagons are stored using the axial coordinate system outlined in the guide by @amitp. But I can easily output their centers as xy-coordinates.

I feel like there might be a solution hidden somewhere in hexbin or RegularPolyCollection. But the former is a histogram method and the latter seems overly complex thanks to the DPI scaling.

So does anyone know of a library that provides a hexagonal grid? It doesn't have to be in matplotlib. I am also quite happy to use ASCII art or switch to R.

Shelli answered 27/10, 2014 at 8:32 Comment(1)
Why don't you use simply a Tkinter canvas ?Schaumberger
G
7

Here comes an implementation that allow you to set hexagonal cell the color you want and also allow to create custom border colors.

This is all hand made, just took 1hour using your reference site. You may have to adapt it to your needs, but seems working.

from tkinter import *

class HexaCanvas(Canvas):
    """ A canvas that provides a create-hexagone method """
    def __init__(self, master, *args, **kwargs):
        Canvas.__init__(self, master, *args, **kwargs)
    
        self.hexaSize = 20
    
    def setHexaSize(self, number):
        self.hexaSize = number
    
    
    def create_hexagone(self, x, y, color = "black", fill="blue", color1=None, color2=None, color3=None, color4=None, color5=None, color6=None):
        """ 
        Compute coordinates of 6 points relative to a center position.
        Point are numbered following this schema :
    
        Points in euclidiean grid:  
                    6
                    
                5       1
                    .
                4       2
            
                    3
    
        Each color is applied to the side that link the vertex with same number to its following.
        Ex : color 1 is applied on side (vertex1, vertex2)
    
        Take care that tkinter ordinate axes is inverted to the standard euclidian ones.
        Point on the screen will be horizontally mirrored.
        Displayed points:
    
                    3
              color3/      \color2      
                4       2
            color4|     |color1
                5       1
              color6\      /color6
                    6
        
        """
        size = self.hexaSize
        Δx = (size**2 - (size/2)**2)**0.5
    
        point1 = (x+Δx, y+size/2)
        point2 = (x+Δx, y-size/2)
        point3 = (x   , y-size  )
        point4 = (x-Δx, y-size/2)
        point5 = (x-Δx, y+size/2)
        point6 = (x   , y+size  )
    
        #this setting allow to specify a different color for each side.
        if color1 == None:
            color1 = color
        if color2 == None:
            color2 = color
        if color3 == None:
            color3 = color
        if color4 == None:
            color4 = color
        if color5 == None:
            color5 = color
        if color6 == None:
            color6 = color
    
        self.create_line(point1, point2, fill=color1, width=2)
        self.create_line(point2, point3, fill=color2, width=2)
        self.create_line(point3, point4, fill=color3, width=2)
        self.create_line(point4, point5, fill=color4, width=2)
        self.create_line(point5, point6, fill=color5, width=2)
        self.create_line(point6, point1, fill=color6, width=2)
    
        if fill != None:
            self.create_polygon(point1, point2, point3, point4, point5, point6, fill=fill)
    
class HexagonalGrid(HexaCanvas):
    """ A grid whose each cell is hexagonal """
    def __init__(self, master, scale, grid_width, grid_height, *args, **kwargs):
    
        Δx     = (scale**2 - (scale/2.0)**2)**0.5
        width  = 2 * Δx * grid_width + Δx
        height = 1.5 * scale * grid_height + 0.5 * scale

        HexaCanvas.__init__(self, master, background='white', width=width, height=height, *args, **kwargs)
        self.setHexaSize(scale)
    
    def setCell(self, xCell, yCell, *args, **kwargs ):
        """ Create a content in the cell of coordinates x and y. Could specify options throught keywords : color, fill, color1, color2, color3, color4; color5, color6"""
    
        #compute pixel coordinate of the center of the cell:
        size = self.hexaSize
        Δx = (size**2 - (size/2)**2)**0.5
    
        pix_x = Δx + 2*Δx*xCell
        if yCell%2 ==1 :
            pix_x += Δx
    
        # Add 5 to avoid clipping on top row of hexes
        pix_y = size + yCell*1.5*size + 5
    
        self.create_hexagone(pix_x, pix_y, *args, **kwargs)
    
    
    
if __name__ == "__main__":
    tk = Tk()

    grid = HexagonalGrid(tk, scale = 50, grid_width=4, grid_height=4)
    grid.grid(row=0, column=0, padx=5, pady=5)

    def correct_quit(tk):
        tk.destroy()
        tk.quit()

    quit = Button(tk, text = "Quit", command = lambda :correct_quit(tk))
    quit.grid(row=1, column=0)

    grid.setCell(0,0, fill='blue')
    grid.setCell(1,0, fill='red')
    grid.setCell(0,1, fill='green')
    grid.setCell(1,1, fill='yellow')
    grid.setCell(2,0, fill='cyan')
    grid.setCell(0,2, fill='teal')
    grid.setCell(2,1, fill='silver')
    grid.setCell(1,2, fill='white')
    grid.setCell(2,2, fill='gray')

    tk.mainloop()

I tried to comment my code properly. If something seems unclear to you, please do not hesitate to ask for explanation.

Good luck Arthur Vaisse.

NB : script running on python 3. Drawing is a little jagged. Improve on Tk canvas can add anti alias as suggested in https://mail.python.org/pipermail/tkinter-discuss/2009-April/001904.html

Gabrielgabriela answered 27/10, 2014 at 11:21 Comment(2)
In Win7, 3.4.2, tk 8.6, tk.quit quits tcl but not not close the tk windows. tk.destroy does.Zephaniah
On my Linux it works properly. I know that in order to quit it properly you have to call tk.destroy and tk.quit methods. But thank to point this mistake here. I'll edit the code.Schaumberger
T
1

You can use my hexalattice python module exactly for that purpose - creating and plotting hexagonal grids:

from hexalattice.hexalattice import *
hex_centers, _ = create_hex_grid(nx=5,
                                 ny=5,
                                 do_plot=True)
                                 
plt.show()    # import matplotlib.pyplot as plt

Producing this plot: enter image description here

See this answer for more examples: this answer

Tisbee answered 11/10, 2020 at 8:58 Comment(0)
R
0

This function creates a hex grid in a given data data's crs and distance measurement.

dist = 5280/2

def create_hex_grid( data , dist ):
    import numpy as np
    from shapely.geometry import Polygon
    from math import sin, radians

    xmin, ymin, xmax, ymax = data.total_bounds

    xcoords = np.arange(xmin, xmax, dist*1.5)
    ycoords = np.arange(ymin, ymax, dist* sin(radians(60)) *0.5)

    def create_grid(xcoords, ycoords, dist):
        points = []
        for count,y in enumerate(ycoords):
            if count % 2 == 0: # even
                for x in xcoords:
                    points.append( (x,y) )
            else:
                for x in xcoords:
                    points.append( (x + (dist*0.75), y) )
        return points

    def hexagon(point, dist):
        # 180 degrees in rad
        from math import pi, sin, cos
        circle = 2*pi
        radius = dist/2

        # get 6 point coordinaties for a hexagon
        x = [ point[0] + radius * cos( i * circle / 6 ) for i in range(6) ]
        y = [ point[1] + radius * sin( i * circle / 6 ) for i in range(6) ]
        x.append( x[0] )
        y.append( y[0] )

        return list(zip(x,y))

    grid_points = create_grid(xcoords, ycoords, dist)
    hex_points = [ hexagon(i, dist) for i in grid_points ]
    hex_polygons = gpd.GeoDataFrame( geometry = [Polygon(i) for i in hex_points], crs=data.crs )
    hex_polygons['id'] = [i for i in range( len( hex_polygons ) ) ]
    return hex_polygons

hex_polygons = create_hex_grid( stewardship , dist )
Ridenhour answered 5/3 at 2:0 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.