codex/graph-subdivision-triangle-pendant-edge-paths.py

graph-subdivision-triangle-pendant-edge-paths.py
"""Draw an original triangle-with-leaf subdivision and its edge paths.

Tested with Python 3.14, matplotlib 3.10.7 and numpy 2.3.5.
Run from _media with MPLBACKEND=Agg; the matching PNG is saved to CWD.
"""

from pathlib import Path

import matplotlib.pyplot as plt
from matplotlib.patches import Circle, FancyArrowPatch


TARGET_EDGES = (("A", "B"), ("B", "C"), ("C", "A"), ("A", "D"))
EDGE_PATHS = (("A", "x", "B"), ("B", "y", "z", "C"),
              ("C", "A"), ("A", "D"))
COLORS = ("#2166ac", "#c66a10", "#248045", "#8055a5")


def vertex(ax, label, xy, original):
    ax.add_patch(Circle(xy, 0.14 if original else 0.105,
                        facecolor="white", edgecolor="#253444" if original else "#777777",
                        linewidth=2.1 if original else 1.5, zorder=3))
    ax.text(*xy, label, ha="center", va="center", fontsize=13,
            weight="bold" if original else "normal", zorder=4)


def main():
    plt.rcParams.update({"font.family": "DejaVu Sans", "font.size": 13})
    fig, axes = plt.subplots(1, 2, figsize=(9, 4.6), dpi=120, facecolor="white")
    fig.subplots_adjust(left=0.025, right=0.975, bottom=0.26, top=0.78, wspace=0.36)
    original = {"A": (-0.7, 0.7), "B": (1.1, 0.7),
                "C": (0.2, -0.8), "D": (-1.6, 1.3)}
    subdivided = dict(original, x=(0.2, 0.7), y=(0.8, 0.2), z=(0.5, -0.3))
    for ax, positions, paths in ((axes[0], original, TARGET_EDGES),
                                  (axes[1], subdivided, EDGE_PATHS)):
        for color, path in zip(COLORS, paths):
            ax.plot([positions[v][0] for v in path], [positions[v][1] for v in path],
                    color=color, linewidth=3, zorder=1)
        for label, xy in positions.items():
            vertex(ax, label, xy, label in original)
        ax.set(xlim=(-1.9, 1.45), ylim=(-1.05, 1.6), aspect="equal")
        ax.axis("off")
    axes[0].set_title("Target H: 4 vertices, 4 edges", fontsize=13, pad=12)
    axes[1].set_title("Subdivision S: 7 vertices, 7 edges", fontsize=13, pad=12)
    fig.suptitle("Subdivision lengthens routes, retaining the junctions", y=0.96,
                 fontsize=16, weight="bold")
    fig.add_artist(FancyArrowPatch((0.455, 0.52), (0.545, 0.52),
                                  transform=fig.transFigure, arrowstyle="-|>",
                                  mutation_scale=20, linewidth=1.7, color="#34495e"))
    for x, text, color in zip((0.16, 0.43, 0.68, 0.88),
                              ("AB → A–x–B", "BC → B–y–z–C", "CA unchanged", "AD unchanged"),
                              COLORS):
        fig.text(x, 0.18, text, color=color, ha="center", fontsize=12)
    fig.text(0.5, 0.095, "A, B, C, D: retained branch vertices     x, y, z: new internal vertices",
             ha="center", fontsize=12)
    fig.text(0.5, 0.04, "Internal vertices are disjoint between routes and avoid all branch vertices.",
             ha="center", fontsize=12)
    output = Path.cwd() / (Path(__file__).stem + ".png")
    fig.savefig(output, facecolor="white", transparent=False)
    plt.close(fig)


if __name__ == "__main__":
    main()