[Rod Stephens Books]
Index Books Python Examples About Rod Contact
[Mastodon] [Bluesky]
[Build Your Own Ray Tracer With Python]

[Beginning Database Design Solutions, Second Edition]

[Beginning Software Engineering, Second Edition]

[Essential Algorithms, Second Edition]

[The Modern C# Challenge]

[WPF 3d, Three-Dimensional Graphics with WPF and C#]

[The C# Helper Top 100]

[Interview Puzzles Dissected]

Title: Draw a hexagonal grid that responds to mouse clicks in Python

[Clicking on a hexagonal grid drawn with Python]

My previous post Draw a hexagonal grid in Python shows how you can draw a hexagonal grid and detect the grid cell under the mouse. This post lets you click on a cell to highlight it. There are quite a few changes between this program and the previous one, but most of them are minor. Here I'll described the basic ideas and show you some key. You can download the example to see more details.

The Hex Class

If you want to use this program as a basis for a more full-featured application, you'll probably want to store information in the grid's cells. For example, you may want to store information about a hex's color, terrain type, and any objects or units that might be in the hex.

To store that kind of information, this program uses a Hex class. The following code shows the class's beginning and constructor.

class Hex: # Define Hex drawing colors. normal_fill = 'cornsilk' normal_outline = 'blue' normal_text_fill = 'black' highlight_fill = 'pink' highlight_outline = 'blue' highlight_text_fill = 'red' font = ('Arial', 10) def __init__(self, row, col, canvas, points): self.row = row self.col = col self.canvas = canvas self.points = points self._is_highlighted = False # Create the hexagon. self.polygon_id = canvas.create_polygon(points, fill=Hex.normal_fill, outline=Hex.normal_outline) # Get the hex's center. self.cx = (points[0][0] + points[3][0]) / 2 self.cy = (points[1][1] + points[4][1]) / 2 # Make a label for this hex. text = f'({row}, {col})' self.label_id = canvas.create_text(self.cx, self.cy, text=text, font=Hex.font, anchor=tk.CENTER)

The class first defines some class variables that hold the colors and font that a hex should have when normal or highlighted.

The constructor saves the hex's row and column, the canvas widget on which it should draw, and the points list that defines its hexagon. It then sets the object's _is_highlighted value to False to indicate that the hex is not initially highlighted.

The constructor then creates its polygon on the canvas widget and saves the returned ID in its polygon_id value.

Next, it calculates the hexagon's center coordinates and saves them in cx and cy. This program won't need those values, but you might in a more complete application.

The constructor then creates a label to display the hex's row and column, and saves the label's ID in the hex's label_id value.

The following code shows how the Hex class provides its is_highlighted property.

@property def is_highlighted(self): return self._is_highlighted @is_highlighted.setter def is_highlighted(self, value): # If the value isn't changing, do nothing. if self._is_highlighted == value: return # Save the value. self._is_highlighted = value # Update the Hex's appearance. if self._is_highlighted: self.canvas.itemconfig(self.polygon_id, fill=Hex.highlight_fill) self.canvas.itemconfig(self.label_id, fill=Hex.highlight_text_fill) self.canvas.itemconfig(self.label_id, text= f'<{self.row}, {self.col}>') else: self.canvas.itemconfig(self.polygon_id, fill=Hex.normal_fill) self.canvas.itemconfig(self.label_id, fill=Hex.normal_text_fill) self.canvas.itemconfig(self.label_id, text= f'({self.row}, {self.col})')

The is_highlighted function simply returns the object's _is_highlighted value.

The property setter first compares the new and old values and returns if they are the same. If the values are different, the code saves the new value and then updates the hex's colors and label text appropriately.

Creating Hexes

The rest of the changes are less interesting.

In the previous example, the draw_hex_grid method created the polygons to draw the grid. The new program renames that method to make_hex_grid because it's not just drawing any more. The following code shows this function.

def make_hex_grid(canvas, xmin, ymin, xmax, ymax, hex_height): ''' Create a hexagonal grid for the indicated area. Return an array Hex objects. ''' # (You might be able to draw the hexagons without # drawing any duplicate edges, but this is a lot easier.) # Make a list of Hex objects. hexes = [] # Loop until a hexagon won't fit. row = 0 while True: # Get the points for the row's first hexagon. points = hex_to_points(xmin, ymin, hex_height, row, 0) # If it doesn't fit, we're done. if points[4][1] >= ymax: break # Add a row to the hexes array. row_hexes = [] hexes.append(row_hexes) # Draw the row. col = 0 while True: # Get the points for the row's next hexagon. points = hex_to_points(xmin, ymin, hex_height, row, col) # If it doesn't fit horizontally, we're done with this row. if points[3][0] >= xmax: break # If it fits vertically, draw it. if points[4][1] < ymax: # Make the Hex object. row_hexes.append(Hex(row, col, canvas, points)) # Move to the next column. col += 1 # Move to the next row. row += 1 # Return the hexes array. return hexes

The function first makes a hexes list. It then uses loops much as before to create the hexes. This time it creates a Hex object for each grid cell and places the new objects in the hexes list.

After it has created all of the hexes, the function returns the list and the main HexGridClickApp saves it.

Mouse Clicks

This program uses the following statement to bind mouse down events on its canvas widget. p class="code">self.canvas.bind('', self.mouse_down)

When you press the mouse down, the following code executes.

def mouse_down(self, event): '''Highlight the clicked hex.''' # Unhilight the previously highlighted hex. if self.highlighted_hex is not None: self.highlighted_hex.is_highlighted = False self.highlighted_hex = None # Find the clicked hex. row, col = point_to_hex(event.x, event.y, self.hex_height) if row < 0 or row >= self.num_rows or \ col < 0 or col >= self.num_cols: return # Get the clicked hex and highlight it. self.highlighted_hex = self.hexes[row][col] self.highlighted_hex.is_highlighted = True

This code first unhighlights the previously highlighted Hex if there is one. It then uses the previous post's point_to_hex function to find the row and column where you clicked. If the row and column is not on the grid, the method returns.

If you clicked on a hex, the code saves the hex you clicked and sets its is_highlighted property to True. The Hex object takes care of updating the hex's appearance.

Conclusion

Download the example to experiment with it and to see additional details. When I have time, I may expand the example again to draw textures on the hexes. (That's a pretty popular request.)
© 2024 - 2025 Rocky Mountain Computer Consulting, Inc. All rights reserved.