codex/pythagorean-square-areas.py

pythagorean-square-areas.py
"""Draw the exact square areas on a 3-4-5 right triangle; output in cwd.

Tested with Python 3.14.4, NumPy 2.3.5 and Matplotlib 3.10.7.
"""

from pathlib import Path

import matplotlib

matplotlib.use("Agg")
import matplotlib.pyplot as plt
from matplotlib.patches import Polygon
import numpy as np


def polygon_area(vertices):
    x, y = vertices.T
    return abs(np.dot(x, np.roll(y, -1)) - np.dot(y, np.roll(x, -1))) / 2


def main():
    triangle = np.array([[0, 0], [4, 0], [0, 3]])
    squares = [
        (np.array([[0, 0], [0, 3], [-3, 3], [-3, 0]]), 9,
         "#e5eefb", "#245b9b", (-1.5, 1.5), "3² = 9"),
        (np.array([[0, 0], [4, 0], [4, -4], [0, -4]]), 16,
         "#fff0d5", "#9b650c", (2, -2.2), "4² = 16"),
        (np.array([[4, 0], [0, 3], [3, 7], [7, 4]]), 25,
         "#e1f2e9", "#287551", (3.5, 3.5), "5² = 25"),
    ]
    assert np.dot(triangle[1] - triangle[0], triangle[2] - triangle[0]) == 0
    assert polygon_area(triangle) == 6
    assert sorted(np.linalg.norm(np.roll(triangle, -1, axis=0) - triangle,
                                 axis=1)) == [3, 4, 5]
    for vertices, area, *_ in squares:
        edges = np.roll(vertices, -1, axis=0) - vertices
        assert polygon_area(vertices) == area
        assert np.all(np.sum(edges * edges, axis=1) == area)
        assert np.all(np.sum(edges * np.roll(edges, -1, axis=0), axis=1) == 0)

    plt.rcParams.update({"font.size": 13, "figure.facecolor": "white"})
    fig, ax = plt.subplots(figsize=(6.8, 7.0), facecolor="white")
    fig.subplots_adjust(left=0.02, right=0.98, bottom=0.08, top=0.88)
    ax.set_facecolor("white")
    ax.set_aspect("equal")
    for vertices, _, face, edge, center, label in squares:
        ax.add_patch(Polygon(vertices, facecolor=face, edgecolor=edge, linewidth=2))
        ax.text(*center, label, color=edge, ha="center", va="center",
                fontsize=18, fontweight="bold")
    ax.add_patch(Polygon(triangle, facecolor="#f5f5f5", edgecolor="#333333",
                         linewidth=2.2))
    ax.plot([0.35, 0.35, 0], [0, 0.35, 0.35], color="#333333", linewidth=1.5)
    ax.text(0.47, 0.29, "90°", fontsize=10)
    ax.text(1.25, 0.93, "Triangle\narea = 6", ha="center", va="center", fontsize=11)
    ax.text(-0.22, 1.7, "3", ha="right", color="#245b9b", fontsize=16,
            bbox={"facecolor": "#e5eefb", "edgecolor": "none", "pad": 1})
    ax.text(2.5, -0.18, "4", ha="center", va="top", color="#9b650c", fontsize=16)
    ax.text(2.25, 1.85, "5", ha="center", color="#287551", fontsize=16,
            bbox={"facecolor": "#e1f2e9", "edgecolor": "none", "pad": 1})
    ax.set_xlim(-3.55, 7.55)
    ax.set_ylim(-4.45, 7.45)
    ax.axis("off")
    fig.suptitle("Pythagorean theorem: square areas", fontsize=19, y=0.97)
    fig.text(0.5, 0.92, "The marked right angle is an assumption, not a guess.",
             ha="center", fontsize=11)
    fig.text(0.5, 0.035, "Lengths: 3, 4, 5     •     Square areas: 9 + 16 = 25",
             ha="center", fontsize=13)
    output = Path.cwd() / (Path(__file__).stem + ".png")
    fig.savefig(output, dpi=140, facecolor="white", transparent=False)
    plt.close(fig)
    print(f"Saved {output.name}; right angle, side lengths and areas checked.")


if __name__ == "__main__":
    main()