| 99 | |
| 100 | |
| 101 | def draw_png() -> Image.Image: |
| 102 | image = Image.new("RGB", (W, H), "white") |
| 103 | draw = ImageDraw.Draw(image) |
| 104 | |
| 105 | for tick in [12, 16, 20, 24, 28, 32, 36]: |
| 106 | x = LEFT + (tick - X_MIN) / (X_MAX - X_MIN) * (RIGHT - LEFT) |
| 107 | draw.line((x, TOP - 25, x, BOTTOM + 15), fill=GRID, width=1) |
| 108 | label = f"{tick}M" |
| 109 | tw, th = text_size(draw, label, F_TICK) |
| 110 | draw.text((x - tw / 2, BOTTOM + 28), label, font=F_TICK, fill=TEXT) |
| 111 | |
| 112 | draw.line((LEFT, BOTTOM + 15, RIGHT, BOTTOM + 15), fill=AXIS, width=2) |
| 113 | draw.text((LEFT, BOTTOM + 61), "TokenCost (million tokens, lower is better)", font=F_AXIS, fill=TEXT) |
| 114 | |
| 115 | # Small legend, kept inside the one-column figure. |
| 116 | lx = LEFT |
| 117 | ly = 24 |
| 118 | draw.ellipse((lx, ly, lx + 16, ly + 16), fill=CLOSED) |
| 119 | draw.text((lx + 24, ly - 3), "Closed", font=F_LEGEND, fill=TEXT) |
| 120 | draw.ellipse((lx + 120, ly, lx + 136, ly + 16), fill=OPEN) |
| 121 | draw.text((lx + 144, ly - 3), "Open", font=F_LEGEND, fill=TEXT) |
| 122 | |
| 123 | for idx, (name, group, tokens) in enumerate(ROWS): |
| 124 | y = y_pos(idx) |
| 125 | x = x_pos(tokens) |
| 126 | color = color_for(group, name) |
| 127 | label_color = TEXT if name != "PaperFlow" else PAPERFLOW |
| 128 | draw_right_text(draw, LEFT - 22, y, name, F_MODEL, label_color) |
| 129 | draw.line((LEFT, y, RIGHT, y), fill=(242, 242, 242), width=1) |
| 130 | draw.ellipse((x - 9, y - 9, x + 9, y + 9), fill=color, outline=color) |
| 131 | value = f"{tokens / 1_000_000:.1f}M" |
| 132 | draw.text((x + 15, y - 12), value, font=F_VALUE, fill=color) |
| 133 | |
| 134 | return image |
| 135 | |
| 136 | |
| 137 | def rgb(c: tuple[int, int, int]) -> str: |