Skip to content

Commit 2c113b0

Browse files
github-actions[bot]claudeMarkusNeusinger
authored
feat(altair): implement crossword-basic (#7456)
## Implementation: `crossword-basic` - python/altair Implements the **python/altair** version of `crossword-basic`. **File:** `plots/crossword-basic/implementations/python/altair.py` **Parent Issue:** #3805 --- :robot: *[impl-generate workflow](https://github.com/MarkusNeusinger/anyplot/actions/runs/26136687136)* --------- Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> Co-authored-by: Markus Neusinger <2921697+MarkusNeusinger@users.noreply.github.com>
1 parent fe44a73 commit 2c113b0

3 files changed

Lines changed: 210 additions & 149 deletions

File tree

Lines changed: 45 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,36 @@
1-
""" pyplots.ai
1+
""" anyplot.ai
22
crossword-basic: Crossword Puzzle Grid
3-
Library: altair 6.0.0 | Python 3.13.11
4-
Quality: 92/100 | Created: 2026-01-15
3+
Library: altair 6.1.0 | Python 3.13.13
4+
Quality: 90/100 | Updated: 2026-05-20
55
"""
66

7+
import os
8+
import sys
9+
10+
11+
# Prevent the script's own directory from shadowing the 'altair' package
12+
sys.path = [p for p in sys.path if not p.endswith("/python")]
13+
714
import altair as alt
815
import numpy as np
916
import pandas as pd
1017

1118

12-
# Data - 15x15 crossword grid with symmetric black cell pattern
19+
# Theme tokens
20+
THEME = os.getenv("ANYPLOT_THEME", "light")
21+
PAGE_BG = "#FAF8F1" if THEME == "light" else "#1A1A17"
22+
INK = "#1A1A17" if THEME == "light" else "#F0EFE8"
23+
INK_SOFT = "#4A4A44" if THEME == "light" else "#B8B7B0"
24+
25+
# Crossword cell colors (theme-adaptive monochrome design)
26+
CELL_ENTRY = "#FFFFFF" if THEME == "light" else "#DEDAD3"
27+
CELL_BLOCK = "#1A1A17" if THEME == "light" else "#3E3E3A"
28+
29+
# Data — 15x15 crossword grid with 180-degree rotational symmetry
1330
np.random.seed(42)
1431
grid_size = 15
15-
16-
# Create symmetric black cell pattern (180-degree rotational symmetry)
17-
# Start with empty grid (0 = white/entry cell, 1 = black/blocked cell)
1832
grid = np.zeros((grid_size, grid_size), dtype=int)
1933

20-
# Define black cell positions for upper-left quadrant + center row/col
21-
# These will be mirrored for symmetry
2234
black_positions = [
2335
(0, 4),
2436
(0, 10),
@@ -43,7 +55,6 @@
4355
(7, 12),
4456
]
4557

46-
# Apply black cells with 180-degree rotational symmetry
4758
for r, c in black_positions:
4859
grid[r, c] = 1
4960
grid[grid_size - 1 - r, grid_size - 1 - c] = 1
@@ -54,58 +65,52 @@
5465

5566
for r in range(grid_size):
5667
for c in range(grid_size):
57-
if grid[r, c] == 1: # Skip black cells
68+
if grid[r, c] == 1:
5869
continue
59-
60-
# Check if this starts an across word (left edge or black cell to left, white to right)
6170
starts_across = (c == 0 or grid[r, c - 1] == 1) and (c < grid_size - 1 and grid[r, c + 1] == 0)
62-
63-
# Check if this starts a down word (top edge or black cell above, white below)
6471
starts_down = (r == 0 or grid[r - 1, c] == 1) and (r < grid_size - 1 and grid[r + 1, c] == 0)
65-
6672
if starts_across or starts_down:
6773
numbers[(r, c)] = clue_num
6874
clue_num += 1
6975

70-
# Build dataframe for grid cells
71-
cells_data = []
72-
for r in range(grid_size):
73-
for c in range(grid_size):
74-
cells_data.append(
75-
{"row": r, "col": c, "is_black": grid[r, c] == 1, "color": "#1a1a1a" if grid[r, c] == 1 else "#ffffff"}
76-
)
77-
76+
# Build dataframes
77+
cells_data = [
78+
{"row": r, "col": c, "color": CELL_BLOCK if grid[r, c] == 1 else CELL_ENTRY}
79+
for r in range(grid_size)
80+
for c in range(grid_size)
81+
]
7882
cells_df = pd.DataFrame(cells_data)
7983

80-
# Build dataframe for clue numbers
81-
numbers_data = []
82-
for (r, c), num in numbers.items():
83-
numbers_data.append({"row": r, "col": c, "number": str(num)})
84-
85-
numbers_df = pd.DataFrame(numbers_data)
84+
numbers_df = pd.DataFrame([{"row": r, "col": c, "number": str(num)} for (r, c), num in numbers.items()])
8685

87-
# Create grid cells chart
86+
# Grid cells layer
8887
cells = (
8988
alt.Chart(cells_df)
90-
.mark_rect(stroke="#333333", strokeWidth=2)
89+
.mark_rect(stroke=INK_SOFT, strokeWidth=1.5)
9190
.encode(x=alt.X("col:O", axis=None), y=alt.Y("row:O", axis=None), color=alt.Color("color:N", scale=None))
92-
.properties(width=900, height=900)
9391
)
9492

95-
# Create clue numbers overlay
93+
# Clue numbers overlay
9694
clue_numbers = (
9795
alt.Chart(numbers_df)
98-
.mark_text(align="left", baseline="top", dx=-12, dy=-12, fontSize=14, fontWeight="bold", color="#333333")
96+
.mark_text(align="left", baseline="top", dx=-12, dy=-12, fontSize=10, fontWeight="bold", color=INK_SOFT)
9997
.encode(x=alt.X("col:O", axis=None), y=alt.Y("row:O", axis=None), text="number:N")
10098
)
10199

102-
# Combine layers and add title
100+
# Compose chart — square 600×600 canvas → 2400×2400 px at scale=4
103101
chart = (
104102
(cells + clue_numbers)
105-
.properties(title=alt.Title("crossword-basic · altair · pyplots.ai", fontSize=32, anchor="middle", offset=20))
106-
.configure_view(strokeWidth=0)
103+
.properties(
104+
width=600,
105+
height=600,
106+
background=PAGE_BG,
107+
title=alt.Title(
108+
"crossword-basic · python · altair · anyplot.ai", fontSize=16, anchor="middle", offset=18, color=INK
109+
),
110+
)
111+
.configure_view(fill=PAGE_BG, strokeWidth=0)
107112
)
108113

109114
# Save outputs
110-
chart.save("plot.png", scale_factor=4.0)
111-
chart.save("plot.html")
115+
chart.save(f"plot-{THEME}.png", scale_factor=4.0)
116+
chart.save(f"plot-{THEME}.html")

0 commit comments

Comments
 (0)