figure.py
5.1 kB · python · 146 lines
1import os23# STYLE45FONT = "Helvetica,Arial,sans-serif"6# PALETTE7INK = "#000000"8MUTED = "#8e8e93"9PALE = "#bababf"10PAPER = "#ffffff"11CONCEPT = {12 "gasket": "#00cad8",13 "carpet": "#008cff",14 "sponge": "#6768fa",15 "prime": "#ff325a",16 "bound": "#ff8f2c",17 "window": "#ff3d40",18 "control": "#8e8e93",19}20PALETTE = {21 "black": "#000000",22 "white": "#ffffff",23 "red": "#ff3d40",24 "red-light": "#ff9d95",25 "red-dark": "#a80016",26 "orange": "#ff8f2c",27 "orange-light": "#ffc093",28 "orange-dark": "#a25400",29 "yellow": "#ffd100",30 "yellow-light": "#ffe591",31 "yellow-dark": "#9e8100",32 "green": "#32cc58",33 "green-light": "#5eee79",34 "green-dark": "#007f2c",35 "mint": "#00d1bb",36 "mint-light": "#48efd8",37 "mint-dark": "#008173",38 "teal": "#00cad8",39 "teal-light": "#48e9f7",40 "teal-dark": "#007c85",41 "cyan": "#1ec9f3",42 "cyan-light": "#86e2ff",43 "cyan-dark": "#007c98",44 "blue": "#008cff",45 "blue-light": "#84bdff",46 "blue-dark": "#00559f",47 "indigo": "#6768fa",48 "indigo-light": "#9ea9ff",49 "indigo-dark": "#3c2abc",50 "purple": "#d332e9",51 "purple-light": "#f08aff",52 "purple-dark": "#870097",53 "pink": "#ff325a",54 "pink-light": "#ff9a9f",55 "pink-dark": "#a50030",56 "brown": "#b18462",57 "brown-light": "#dfaf8c",58 "brown-dark": "#754c2b",59 "gray": "#8e8e93",60 "gray-light": "#bababf",61 "gray-dark": "#56565a",62}63# PALETTE END64STROKE = 1.265THIN = 0.666MARGIN = 366768def esc(s):69 return s.replace("&", "&").replace("<", "<").replace(">", ">")7071def header(width, height):72 return ['<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 %d %d" width="%d" height="%d">' % (width, height, width, height), '<rect width="%d" height="%d" fill="%s"/>' % (width, height, PAPER)]7374def text(x, y, s, size=11, fill=INK, anchor="start", weight=None):75 w = ' font-weight="%s"' % weight if weight else ""76 return '<text x="%.1f" y="%.1f" font-family="%s" font-size="%d" fill="%s" text-anchor="%s"%s>%s</text>' % (x, y, FONT, size, fill, anchor, w, esc(s))7778def line(x1, y1, x2, y2, stroke=INK, width=STROKE):79 return '<line x1="%.1f" y1="%.1f" x2="%.1f" y2="%.1f" stroke="%s" stroke-width="%.1f"/>' % (x1, y1, x2, y2, stroke, width)8081def rect(x, y, w, h, fill, opacity=1.0):82 return '<rect x="%.1f" y="%.1f" width="%.1f" height="%.1f" fill="%s" opacity="%.2f"/>' % (x, y, w, h, fill, opacity)8384def circle(x, y, r, fill):85 return '<circle cx="%.1f" cy="%.1f" r="%.1f" fill="%s"/>' % (x, y, r, fill)8687def save(parts, path):88 with open(path, "w") as handle:89 handle.write("\n".join(parts + ["</svg>"]) + "\n")9091# STYLE END9293# DRAW9495POLYGONAL = CONCEPT["carpet"]96CENTERED = CONCEPT["sponge"]9798PANELS = [99 (1, [(0, 0)], "k^2", "square", "A000290", POLYGONAL),100 (3, [(0, 0), (1, 0)], "2k^2 - k", "hexagonal", "A000384", POLYGONAL),101 (7, [(0, 0), (1, 0), (0, 1)], "3k^2 - 2k", "octagonal", "A000567", POLYGONAL),102 (9, [(0, 0), (1, 1)], "2k^2 - 2k + 1", "centered square", "A001844", CENTERED),103 (11, [(0, 0), (1, 0), (1, 1)], "3k^2 - 3k + 1", "centered hexagonal", "A003215", CENTERED),104 (15, [(0, 0), (1, 0), (0, 1), (1, 1)], "4k^2 - 4k + 1", "centered octagonal", "A016754", CENTERED),105]106107def panel(parts, x, y, spec, side, unit):108 number, rule, poly, family, entry, colour = spec109 keep = set(rule)110 for i in range(side):111 for j in range(side):112 cx = x + i * unit113 cy = y + (side - 1 - j) * unit114 if (i & 1, j & 1) in keep:115 shell = max(i, j) >= side - 2116 parts.append(rect(cx, cy, unit - 1, unit - 1, colour, 0.42 if shell else 1.0))117 else:118 parts.append(rect(cx, cy, unit - 1, unit - 1, PALE, 0.55))119 base = y + side * unit120 parts.append(text(x, base + 15, "code %d" % number, size=10, fill=MUTED))121 parts.append(text(x, base + 31, poly, size=12, weight="bold"))122 parts.append(text(x, base + 46, "%s, %s" % (family, entry), size=10, fill=MUTED))123124def main():125 side, unit = 5, 20126 slot, lift = 232, 172127 width, height = 2 * MARGIN + 2 * slot + side * unit, 130 + 2 * lift128 parts = header(width, height)129 parts.append(text(MARGIN, 40, "Six designs of the plane, and the classical family each one counts", size=14, weight="bold"))130 parts.append(text(MARGIN, 58, "filled cells at side n = 2k - 1 = 5; the pale ring is the shell the step from k = 2 adds", size=11, fill=MUTED))131 for index, spec in enumerate(PANELS):132 x = MARGIN + (index % 3) * slot133 y = 78 + (index // 3) * lift134 panel(parts, x, y, spec, side, unit)135 foot = height - MARGIN + 6136 parts.append(rect(MARGIN, foot - 10, 11, 11, POLYGONAL))137 parts.append(text(MARGIN + 17, foot, "both coordinates odd: dropped, polygonal", size=11, fill=INK))138 parts.append(rect(MARGIN + 268, foot - 10, 11, 11, CENTERED))139 parts.append(text(MARGIN + 285, foot, "kept, centered polygonal", size=11, fill=INK))140 out = os.path.join(os.path.dirname(os.path.abspath(__file__)), "..", "figures")141 os.makedirs(out, exist_ok=True)142 save(parts, os.path.join(out, "families.svg"))143 print("drew figures/families.svg")144145if __name__ == "__main__":146 main()