figure.py

5.0 kB · python · 157 lines

1import os2from math import gcd34# STYLE56FONT = "Helvetica,Arial,sans-serif"7# PALETTE8INK = "#000000"9MUTED = "#8e8e93"10PALE = "#bababf"11PAPER = "#ffffff"12CONCEPT = {13    "gasket": "#00cad8",14    "carpet": "#008cff",15    "sponge": "#6768fa",16    "prime": "#ff325a",17    "bound": "#ff8f2c",18    "window": "#ff3d40",19    "control": "#8e8e93",20}21PALETTE = {22    "black": "#000000",23    "white": "#ffffff",24    "red": "#ff3d40",25    "red-light": "#ff9d95",26    "red-dark": "#a80016",27    "orange": "#ff8f2c",28    "orange-light": "#ffc093",29    "orange-dark": "#a25400",30    "yellow": "#ffd100",31    "yellow-light": "#ffe591",32    "yellow-dark": "#9e8100",33    "green": "#32cc58",34    "green-light": "#5eee79",35    "green-dark": "#007f2c",36    "mint": "#00d1bb",37    "mint-light": "#48efd8",38    "mint-dark": "#008173",39    "teal": "#00cad8",40    "teal-light": "#48e9f7",41    "teal-dark": "#007c85",42    "cyan": "#1ec9f3",43    "cyan-light": "#86e2ff",44    "cyan-dark": "#007c98",45    "blue": "#008cff",46    "blue-light": "#84bdff",47    "blue-dark": "#00559f",48    "indigo": "#6768fa",49    "indigo-light": "#9ea9ff",50    "indigo-dark": "#3c2abc",51    "purple": "#d332e9",52    "purple-light": "#f08aff",53    "purple-dark": "#870097",54    "pink": "#ff325a",55    "pink-light": "#ff9a9f",56    "pink-dark": "#a50030",57    "brown": "#b18462",58    "brown-light": "#dfaf8c",59    "brown-dark": "#754c2b",60    "gray": "#8e8e93",61    "gray-light": "#bababf",62    "gray-dark": "#56565a",63}64# PALETTE END65STROKE = 1.266THIN = 0.667MARGIN = 366869def esc(s):70    return s.replace("&", "&amp;").replace("<", "&lt;").replace(">", "&gt;")7172def header(width, height):73    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)]7475def text(x, y, s, size=11, fill=INK, anchor="start", weight=None):76    w = ' font-weight="%s"' % weight if weight else ""77    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))7879def line(x1, y1, x2, y2, stroke=INK, width=STROKE):80    return '<line x1="%.1f" y1="%.1f" x2="%.1f" y2="%.1f" stroke="%s" stroke-width="%.1f"/>' % (x1, y1, x2, y2, stroke, width)8182def rect(x, y, w, h, fill, opacity=1.0):83    return '<rect x="%.1f" y="%.1f" width="%.1f" height="%.1f" fill="%s" opacity="%.2f"/>' % (x, y, w, h, fill, opacity)8485def circle(x, y, r, fill):86    return '<circle cx="%.1f" cy="%.1f" r="%.1f" fill="%s"/>' % (x, y, r, fill)8788def save(parts, path):89    with open(path, "w") as handle:90        handle.write("\n".join(parts + ["</svg>"]) + "\n")9192# STYLE END9394# RAYS9596GASKET = ((0, 0), (1, 0), (0, 1))97SWAP01 = ((0, 1), (1, 0), (2, 2))98RAY = CONCEPT["prime"]99DESIGN = CONCEPT["carpet"]100101def points(design, level):102    pts = [(0, 0)]103    for i in range(level):104        p = 3 ** i105        pts = [(x + dx * p, y + dy * p) for (x, y) in pts for (dx, dy) in design]106    return pts107108def panel(x0, y0, side, span):109    def place(x, y):110        return (x0 + side * x / span, y0 + side - side * y / span)111    return place112113def svg():114    w, h, side = 760, 400, 300115    pad = 34116    out = header(w, h)117    span = 3 ** 4118    place = panel(pad, pad + 12, side, span)119    ray = [(3 * k, k) for k in range(1, span // 3 + 1)]120    rayset = set(ray)121    gasket = points(GASKET, 4)122    gasketset = set(gasket)123    out.append('<line x1="%.1f" y1="%.1f" x2="%.1f" y2="%.1f" stroke="%s" stroke-width="1.4" opacity="0.75"/>' % (place(0, 0) + place(span, span / 3) + (RAY,)))124    for (x, y) in gasket:125        cx, cy = place(x, y)126        out.append(rect(cx - 1.6, cy - 1.6, 3.2, 3.2, RAY if (x, y) in rayset else CONCEPT["gasket"]))127    for (x, y) in ray:128        if (x, y) in gasketset:129            cx, cy = place(x, y)130            out.append('<circle cx="%.2f" cy="%.2f" r="4.6" fill="none" stroke="%s" stroke-width="1.4"/>' % (cx, cy, RAY))131    out.append(text(pad, pad + 2, "the gasket, level 4", size=15))132    out.append(text(pad, pad + side + 32, "the ray (3,1) carries 4 points", size=13, fill=RAY))133    x1 = w - pad - side134    span2 = 3 ** 3135    place2 = panel(x1, pad + 12, side, span2)136    pts = sorted(points(SWAP01, 3))137    for (x, y) in pts:138        g = gcd(x, y)139        px, py = x / g, y / g140        scale = span2 / max(px, py)141        out.append('<line x1="%.1f" y1="%.1f" x2="%.1f" y2="%.1f" stroke="%s" stroke-width="0.7" opacity="0.35"/>' % (place2(0, 0) + place2(px * scale, py * scale) + (DESIGN,)))142    for (x, y) in pts:143        cx, cy = place2(x, y)144        out.append(circle(cx, cy, 3.1, DESIGN))145    out.append(text(x1, pad + 2, "a diagonal design, level 3", size=15))146    out.append(text(x1, pad + side + 32, "27 points, 27 rays, no two shared", size=13, fill=DESIGN))147    return out148149def main():150    here = os.path.dirname(os.path.abspath(__file__))151    out = os.path.join(here, "..", "figures")152    os.makedirs(out, exist_ok=True)153    save(svg(), os.path.join(out, "rays.svg"))154    print("wrote figures/rays.svg")155156if __name__ == "__main__":157    main()