|
1 | | -""" pyplots.ai |
| 1 | +""" anyplot.ai |
2 | 2 | crossword-basic: Crossword Puzzle Grid |
3 | | -Library: seaborn 0.13.2 | Python 3.13.11 |
4 | | -Quality: 92/100 | Created: 2026-01-15 |
| 3 | +Library: seaborn 0.13.2 | Python 3.13.13 |
| 4 | +Quality: 88/100 | Updated: 2026-05-20 |
5 | 5 | """ |
6 | 6 |
|
| 7 | +import os |
| 8 | + |
7 | 9 | import matplotlib.pyplot as plt |
8 | 10 | import numpy as np |
9 | 11 | import seaborn as sns |
10 | 12 |
|
11 | 13 |
|
12 | | -# Set style |
13 | | -sns.set_style("white") |
| 14 | +# Theme tokens |
| 15 | +THEME = os.getenv("ANYPLOT_THEME", "light") |
| 16 | +PAGE_BG = "#FAF8F1" if THEME == "light" else "#1A1A17" |
| 17 | +ELEVATED_BG = "#FFFDF6" if THEME == "light" else "#242420" |
| 18 | +INK = "#1A1A17" if THEME == "light" else "#F0EFE8" |
| 19 | +INK_SOFT = "#4A4A44" if THEME == "light" else "#B8B7B0" |
| 20 | + |
| 21 | +sns.set_theme( |
| 22 | + style="ticks", |
| 23 | + rc={ |
| 24 | + "figure.facecolor": PAGE_BG, |
| 25 | + "axes.facecolor": PAGE_BG, |
| 26 | + "axes.edgecolor": INK_SOFT, |
| 27 | + "axes.labelcolor": INK, |
| 28 | + "text.color": INK, |
| 29 | + "xtick.color": INK_SOFT, |
| 30 | + "ytick.color": INK_SOFT, |
| 31 | + "grid.color": INK, |
| 32 | + "grid.alpha": 0.10, |
| 33 | + "legend.facecolor": ELEVATED_BG, |
| 34 | + "legend.edgecolor": INK_SOFT, |
| 35 | + }, |
| 36 | +) |
14 | 37 |
|
15 | 38 | # Data: 15x15 crossword grid with 180-degree rotational symmetry |
16 | | -np.random.seed(42) |
17 | 39 | grid_size = 15 |
18 | | - |
19 | | -# Create symmetric black cell pattern (1 = blocked, 0 = entry) |
20 | 40 | grid = np.zeros((grid_size, grid_size), dtype=int) |
21 | 41 |
|
22 | | -# Define blocked cells for top half (will be mirrored for symmetry) |
23 | 42 | blocked_positions = [ |
24 | 43 | (0, 4), |
25 | 44 | (0, 10), |
|
43 | 62 | (7, 7), |
44 | 63 | ] |
45 | 64 |
|
46 | | -# Apply blocked cells with 180-degree rotational symmetry |
47 | 65 | for r, c in blocked_positions: |
48 | 66 | grid[r, c] = 1 |
49 | 67 | grid[grid_size - 1 - r, grid_size - 1 - c] = 1 |
|
55 | 73 | for c in range(grid_size): |
56 | 74 | if grid[r, c] == 1: |
57 | 75 | continue |
58 | | - # Check if starts across word (left edge or blocked cell to left, and has room) |
59 | 76 | starts_across = (c == 0 or grid[r, c - 1] == 1) and (c < grid_size - 1 and grid[r, c + 1] == 0) |
60 | | - # Check if starts down word (top edge or blocked cell above, and has room) |
61 | 77 | starts_down = (r == 0 or grid[r - 1, c] == 1) and (r < grid_size - 1 and grid[r + 1, c] == 0) |
62 | | - |
63 | 78 | if starts_across or starts_down: |
64 | 79 | numbers[(r, c)] = clue_num |
65 | 80 | clue_num += 1 |
66 | 81 |
|
67 | | -# Plot using seaborn heatmap |
68 | | -fig, ax = plt.subplots(figsize=(12, 12)) |
| 82 | +# Cell colors: open cells are warm white, blocked cells near-black (data encoding) |
| 83 | +# Dark mode: use pure black for blocked cells so they contrast with #1A1A17 background |
| 84 | +cell_open = "#FFFFFF" if THEME == "light" else "#FAF8F1" |
| 85 | +cell_blocked = "#1A1A17" if THEME == "light" else "#000000" |
| 86 | +NUMBER_INK = "#1A1A17" |
69 | 87 |
|
70 | | -# Create color map: 0 (white cells) = white, 1 (blocked) = black |
71 | | -cmap = sns.color_palette(["white", "#1a1a1a"]) |
| 88 | +# Plot |
| 89 | +fig, ax = plt.subplots(figsize=(6, 6), dpi=400, facecolor=PAGE_BG) |
72 | 90 |
|
73 | | -# Plot heatmap without annotations |
| 91 | +cmap = sns.color_palette([cell_open, cell_blocked]) |
74 | 92 | sns.heatmap( |
75 | 93 | grid, |
76 | 94 | ax=ax, |
77 | 95 | cmap=cmap, |
78 | 96 | cbar=False, |
79 | 97 | square=True, |
80 | | - linewidths=2, |
81 | | - linecolor="#306998", |
| 98 | + linewidths=1.0, |
| 99 | + linecolor=INK_SOFT, |
82 | 100 | xticklabels=False, |
83 | 101 | yticklabels=False, |
84 | 102 | ) |
85 | 103 |
|
86 | | -# Add clue numbers to appropriate cells |
| 104 | +# Add clue numbers to top-left corner of numbered cells |
87 | 105 | for (r, c), num in numbers.items(): |
88 | | - # Position numbers in top-left corner of cell |
89 | | - ax.text(c + 0.15, r + 0.25, str(num), fontsize=11, fontweight="bold", color="#306998", ha="left", va="top") |
| 106 | + ax.text(c + 0.12, r + 0.28, str(num), fontsize=9, fontweight="bold", color=NUMBER_INK, ha="left", va="top") |
90 | 107 |
|
91 | 108 | # Style |
92 | | -ax.set_title("crossword-basic · seaborn · pyplots.ai", fontsize=24, fontweight="bold", pad=20) |
| 109 | +ax.set_title("crossword-basic · python · seaborn · anyplot.ai", fontsize=12, fontweight="medium", color=INK, pad=14) |
93 | 110 |
|
94 | | -# Remove axis spines and add outer border |
95 | 111 | for spine in ax.spines.values(): |
96 | 112 | spine.set_visible(True) |
97 | | - spine.set_color("#306998") |
98 | | - spine.set_linewidth(3) |
| 113 | + spine.set_color(INK_SOFT) |
| 114 | + spine.set_linewidth(1.5) |
99 | 115 |
|
100 | 116 | plt.tight_layout() |
101 | | -plt.savefig("plot.png", dpi=300, bbox_inches="tight", facecolor="white") |
| 117 | +plt.savefig(f"plot-{THEME}.png", dpi=400, bbox_inches="tight", facecolor=PAGE_BG) |
0 commit comments