Title: Demonstrate Napoleon's theorem in Python
Napoleon's theorem says that, if you attach equilateral triangles either inside or outside of a triangle, then their centers for another equilateral triangle. This program lets you demonstrate this theorem.
You can see how it works in the picture on the right. The user defines the red triangle by clicking three points. The picture on the left draws three green equilateral triangles on the outside of the triangle's edges. The green circles show the centers of those triangles. Those centers define the filled green triangle, which is equilateral.
The picture on the right is harder to understand. It draws three blue triangles on the insides of the red triangle's edges. The picture is confusing because those triangles stick out on the opposite sides of the red triangle. The blue circles show the centers of the blue triangles and the filled blue triangle shows the equilateral triangle that they define.
Whether you pick the red triangle's points in clockwise or counterclockwise order determines whether the interior and exterior triangles are on the left or right.
This program has two main tasks: get three points from the user and draw the triangles. The second task requires a bit more work, which is easier with a few helper methods.
Picking Points
When it starts, the program uses the following code to create the self.points list.
# Watch for mouse clicks.
self.points = []
self.canvas1.bind("", self.mouse_down)
This code creates the empty list and then binds left mouse button press events for the program's left Canvas widget. When you press the mouse button down over that canvas, the following event handler executes.
def mouse_down(self, event):
'''Save the point.'''
# If we already have two points, start anew.
if len(self.points) == 3:
self.points = []
# Save this point.
self.points.append((event.x, event.y))
# Update the display.
self.update_display()
If the self.points list already holds three points, then they are from a previous triangle so the code clears the list to start over.
The program then adds the mouse position to the points list and calls the update_display method to display the points and triangles.
update_display
The following code shows the update_display method.
def update_display(self):
'''Draw the points and arcs if defined.'''
# Clear the canvas.
self.canvas1.delete(tk.ALL)
self.canvas2.delete(tk.ALL)
# Drawing parameters.
r = 5
# If we have three points, find the triangles, etc.
if len(self.points) == 3:
# Find the triangles.
triangles = []
for i in range(len(self.points)):
i1 = (i + 1) % len(self.points)
segment = [self.points[i], self.points[i1]]
triangles.append(find_segment_triangles(segment))
# Draw the triangles.
triangles1 = [triangle[0] for triangle in triangles]
self.draw_triangles(self.canvas1, 'light green', 'green',
triangles1)
triangles2 = [triangle[1] for triangle in triangles]
self.draw_triangles(self.canvas2, 'light blue', 'blue',
triangles2)
# Draw the user's triangle.
self.canvas1.create_polygon(
self.points, fill='', outline='red')
self.canvas2.create_polygon(
self.points, fill='', outline='red')
# Draw the points.
for point in self.points:
self.canvas1.create_oval(
point[0] - r, point[1] - r,
point[0] + r, point[1] + r,
fill='pink')
self.canvas2.create_oval(
point[0] - r, point[1] - r,
point[0] + r, point[1] + r,
fill='pink')
This code first clears both of the program's two Canvas widgets. Then, if we have three points, it performs three tasks.
First, the code loops through the points and calls find_segment_triangles (described shortly) for each pair of points. That function finds the inner and outer triangles for the line segment. The code adds the returned triangles to the triangles list.
Second, the program draws the triangles. First it uses a list comprehension to pull the first triangle for each segment into a new list. It then calls draw_triangles (also described shortly) to draw those triangles, their centers, and their Napoleon triangle. It then repeats those steps to draw the second triangle for each segment. Note that the program doesn't know whether the first or second set of triangles contains the inner or outer triangles. That depends on whether you picked the points in clockwise or counterclockwise order.
Third, the program draws the triangle that you selected by clicking on the left Canvas.
Whether we have three points or not, the program, then draws whatever points it has in the self.points list on both Canvas widgets.
find_segment_triangles
The following find_segment_triangles function finds the two equilateral triangles on either side of a line segment.
def find_segment_triangles(segment):
'''Make an equilateral triangle using this segment as one side..'''
# Make a Vector2 object for the segment.
p1 = pygame.Vector2(segment[0])
p2 = pygame.Vector2(segment[1])
v12 = p2 - p1
# Rotate the vector +/-60 degrees.
p3 = p1 + v12.rotate(60)
p4 = p1 + v12.rotate(-60)
# Convert from Vector2 objects to (X Y) tuples.
p1 = segment[0]
p2 = segment[1]
p3 = (p3.x, p3.y)
p4 = (p4.x, p4.y)
# Return the two triangles.
return ((p1, p2, p3), (p1, p2, p4))
This method uses some Pygame vector tricks to make finding the triangles easier. First it converts the segment's two points into Vector2 objects. It then subtracts them to get a vector pointing from the first point to the second.
Remember, the goal is to make two equilateral triangles attached to the line segment. We already have two vertices on that triangle (the points that define the segment), so we just need the third vertex. One way to get that vertex is to copy the segment (so the copy has the same length as the original) and then rotate the copy by 60° (or -60°).
The code uses the Vector2 class's rotate method to rotate the initial vector by 60° and -60°. To get the coordinates of the triangles' third vertices, the code adds the rotated vector to the original segment's first point.
The code finishes by converting the Vector2 objects back into (X, Y) coordinate tuples and returning two tuples that hold the points that make up the two equilateral triangles.
draw_triangles
The following draw_triangles method draws the equilateral triangles and their Napoleon triangle.
def draw_triangles(self, canvas, fill, outline, triangles):
'''Draw these triangles and Napoleon triangle.'''
# Find the center points.
centers = []
for triangle in triangles:
# Find this triangle's center.
center = [0, 0]
for point in triangle:
center[0] += point[0]
center[1] += point[1]
center[0] /= len(triangle)
center[1] /= len(triangle)
centers.append(center)
# Verify that the centers form an equilateral triangle.
lengths = []
for i in range(len(triangles)):
i1 = (i + 1) % len(triangles)
lengths.append(math.hypot(
centers[i][0] - centers[i1][0],
centers[i][1] - centers[i1][1]))
if math.isclose(lengths[0], lengths[1]) and \
math.isclose(lengths[1], lengths[2]) and \
math.isclose(lengths[2], lengths[0]):
print('Equilateral')
else:
print('NOT equilateral')
# Draw the Napoleon triangle.
canvas.create_polygon(centers, fill=fill, outline=outline)
# Draw the triangle centers.
r = 5
for center in centers:
canvas.create_oval(
center[0] - r, center[1] - r,
center[0] + r, center[1] + r,
fill=fill, outline=outline)
# Draw the triangles.
for triangle in triangles:
# Draw this triangle.
canvas.create_polygon(
triangle, fill='', outline=outline)
# Draw the center.
canvas.create_oval(
center[0] - r, center[1] - r,
center[0] + r, center[1] + r,
fill=fill, outline=outline)
This method first finds the triangles' centers. For each triangle, it loops through the triangle's vertices, adds their coordinates, and divides by three to get the average of the points' coordinates. That gives the triangle's centroid, and the code adds it to the centers list.
To verify that the triangle defined by the centers is equilateral, the code then checks that the distances between the pairs of centers are the same.
Having found the triangles' centers, the method uses them to draw the filled Napoleon triangle.
Next, the code draws the triangles' centers. It then draws the original equilateral triangles and their centers.
Conclusion
Napoleon's theorem is often attributed to Napoleon Bonaparte, who was an amateur mathematician, but it's likely that it was discovered by someone else, possibly his friend Lorenzo Mascheroni. In any case, it's an interesting result.
Download the example to experiment with it and to see additional details.
|