Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
191 changes: 191 additions & 0 deletions plots/maze-printable/implementations/pygal.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,191 @@
""" pyplots.ai
maze-printable: Printable Maze Puzzle
Library: pygal 3.1.0 | Python 3.13.11
Quality: 90/100 | Created: 2026-01-07
"""

import os
import re

import cairosvg
import numpy as np
import pygal
from pygal.style import Style


# Seed for reproducibility
np.random.seed(42)

# Maze dimensions (25x25 as specified in spec)
maze_width = 25
maze_height = 25

# Generate maze using DFS algorithm
# Each cell: 0 = wall, 1 = passage
grid_h = maze_height * 2 + 1
grid_w = maze_width * 2 + 1
maze = np.zeros((grid_h, grid_w), dtype=int)

# Initialize cells (passages between walls)
for y in range(maze_height):
for x in range(maze_width):
maze[y * 2 + 1, x * 2 + 1] = 1

# DFS maze generation
stack = [(0, 0)]
visited = set()
visited.add((0, 0))

directions = [(0, 1), (1, 0), (0, -1), (-1, 0)]

while stack:
cx, cy = stack[-1]
neighbors = []
for dx, dy in directions:
nx, ny = cx + dx, cy + dy
if 0 <= nx < maze_width and 0 <= ny < maze_height and (nx, ny) not in visited:
neighbors.append((nx, ny, dx, dy))

if neighbors:
idx = np.random.randint(len(neighbors))
nx, ny, dx, dy = neighbors[idx]
# Remove wall between current and neighbor
maze[cy * 2 + 1 + dy, cx * 2 + 1 + dx] = 1
visited.add((nx, ny))
stack.append((nx, ny))
else:
stack.pop()

# Create entrance (top-left) and exit (bottom-right)
maze[0, 1] = 1 # Entrance at top
maze[grid_h - 1, grid_w - 2] = 1 # Exit at bottom

# Mark start and goal positions in the maze data
# Start: first cell after entrance (row 1, col 1)
start_y, start_x = 1, 1
# Goal: last cell before exit (row grid_h-2, col grid_w-2)
goal_y, goal_x = grid_h - 2, grid_w - 2

# All walls are black - create enough black entries for each row
colors = tuple(["#000000"] * grid_h)

# Custom style - clean black and white for printability
custom_style = Style(
background="white",
plot_background="white",
foreground="#000000",
foreground_strong="#000000",
foreground_subtle="#000000",
colors=colors,
title_font_size=96,
label_font_size=1,
major_label_font_size=1,
legend_font_size=1,
value_font_size=1,
font_family="monospace",
)

# Use Dot chart to create grid representation
chart = pygal.Dot(
width=3600,
height=3600,
style=custom_style,
title="maze-printable · pygal · pyplots.ai",
show_legend=False,
show_x_labels=False,
show_y_labels=False,
dots_size=32,
margin=100,
)

# Add wall rows - walls show as black dots, passages are empty
for y in range(grid_h):
row_data = []
for x in range(grid_w):
if maze[y, x] == 0: # Wall
row_data.append(1)
else: # Passage
row_data.append(None)
chart.add("", row_data)

# First, create a helper chart with dots at start and goal positions to find exact coordinates
helper_chart = pygal.Dot(
width=3600,
height=3600,
style=custom_style,
title="maze-printable · pygal · pyplots.ai",
show_legend=False,
show_x_labels=False,
show_y_labels=False,
dots_size=32,
margin=100,
)

# Add rows with dots only at start and goal positions
for y in range(grid_h):
row_data = []
for x in range(grid_w):
if (x == start_x and y == start_y) or (x == goal_x and y == goal_y):
row_data.append(1)
else:
row_data.append(None)
helper_chart.add("", row_data)

# Extract circle positions from helper chart
helper_svg = helper_chart.render().decode("utf-8")
circles = re.findall(r'<circle[^>]*cx="([^"]+)"[^>]*cy="([^"]+)"', helper_svg)

# Get start and goal positions (first circle is start, second is goal)
# Note: pygal applies a transform to the plot group, so we need to add offsets
# The plot group has transform="translate(margin, title_area_height + margin)"
# margin = 100, title_area_height ~ 106 (based on title_font_size 96)
plot_x_offset = 100
plot_y_offset = 206 # Accounts for title area and margin

if len(circles) >= 2:
s_x = float(circles[0][0]) + plot_x_offset
s_y = float(circles[0][1]) + plot_y_offset
g_x = float(circles[1][0]) + plot_x_offset
g_y = float(circles[1][1]) + plot_y_offset
else:
# Fallback positions
s_x, s_y = 300, 400
g_x, g_y = 3350, 3350

# Render the main maze chart
svg_string = chart.render().decode("utf-8")

# Create S and G text elements with bold styling - sized to fit within a cell
# Cell spacing is approximately 64px, so font-size of 50 fits well
s_marker = f'''<text x="{s_x}" y="{s_y}" font-family="Arial, sans-serif" font-size="50" font-weight="bold" fill="#000000" text-anchor="middle" dominant-baseline="central">S</text>'''
g_marker = f'''<text x="{g_x}" y="{g_y}" font-family="Arial, sans-serif" font-size="50" font-weight="bold" fill="#000000" text-anchor="middle" dominant-baseline="central">G</text>'''

# Insert markers before the closing </svg> tag
svg_with_markers = svg_string.replace("</svg>", f"{s_marker}\n{g_marker}\n</svg>")

# Write modified SVG and convert to PNG
with open("plot_temp.svg", "w", encoding="utf-8") as f:
f.write(svg_with_markers)

# Use cairosvg to convert SVG to PNG
cairosvg.svg2png(bytestring=svg_with_markers.encode("utf-8"), write_to="plot.png")

# Also save HTML version with markers
html_template = f"""<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>maze-printable · pygal · pyplots.ai</title>
</head>
<body style="margin:0;padding:0;background:white;">
{svg_with_markers}
</body>
</html>"""

with open("plot.html", "w", encoding="utf-8") as f:
f.write(html_template)

# Clean up temp file
if os.path.exists("plot_temp.svg"):
os.remove("plot_temp.svg")
207 changes: 207 additions & 0 deletions plots/maze-printable/metadata/pygal.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,207 @@
library: pygal
specification_id: maze-printable
created: '2026-01-07T20:23:18Z'
updated: '2026-01-07T20:44:36Z'
generated_by: claude-opus-4-5-20251101
workflow_run: 20795218976
issue: 3232
python_version: 3.13.11
library_version: 3.1.0
preview_url: https://storage.googleapis.com/pyplots-images/plots/maze-printable/pygal/plot.png
preview_thumb: https://storage.googleapis.com/pyplots-images/plots/maze-printable/pygal/plot_thumb.png
preview_html: https://storage.googleapis.com/pyplots-images/plots/maze-printable/pygal/plot.html
quality_score: 90
review:
strengths:
- Creative adaptation of pygal Dot chart for maze rendering
- Proper DFS maze generation algorithm ensuring single solution
- Clean black-and-white design optimized for printing
- Correct title format and square aspect ratio
- SVG post-processing to add S/G markers at calculated positions
- Proper entrance and exit gaps at maze borders
weaknesses:
- S and G markers could be slightly more prominent (larger font size would improve
visibility)
- Passage width is adequate but on the tighter side for comfortable pen/pencil marking
image_description: The plot displays a 25x25 printable maze puzzle rendered using
pygal's Dot chart. The maze consists of black circular dots forming the walls
on a white background, creating clear passages between them. The title "maze-printable
· pygal · pyplots.ai" appears at the top in a clean monospace font. An "S" marker
indicates the start position near the top-left corner (with an entrance gap at
the top border), and a "G" marker indicates the goal position near the bottom-right
corner (with an exit gap at the bottom border). The maze uses the square 3600×3600
format, with the maze grid filling the canvas well. The black-and-white design
is print-friendly with good contrast.
criteria_checklist:
visual_quality:
score: 36
max: 40
items:
- id: VQ-01
name: Text Legibility
score: 8
max: 10
passed: true
comment: Title is clearly readable. S and G markers are visible but could
be slightly larger for better visibility.
- id: VQ-02
name: No Overlap
score: 8
max: 8
passed: true
comment: No overlapping elements; walls and passages are clearly distinguished
- id: VQ-03
name: Element Visibility
score: 8
max: 8
passed: true
comment: Dot size creates clear walls with appropriate passage widths
- id: VQ-04
name: Color Accessibility
score: 5
max: 5
passed: true
comment: Pure black on white provides maximum contrast and is colorblind-safe
- id: VQ-05
name: Layout Balance
score: 5
max: 5
passed: true
comment: Maze fills canvas well with balanced margins; good use of square
format
- id: VQ-06
name: Axis Labels
score: 0
max: 2
passed: true
comment: N/A for maze (no axes needed)
- id: VQ-07
name: Grid & Legend
score: 2
max: 2
passed: true
comment: No grid or legend needed for maze; clean implementation
spec_compliance:
score: 25
max: 25
items:
- id: SC-01
name: Plot Type
score: 8
max: 8
passed: true
comment: Correct maze puzzle visualization using creative dot-based approach
- id: SC-02
name: Data Mapping
score: 5
max: 5
passed: true
comment: Walls correctly mapped as black dots, passages as empty space
- id: SC-03
name: Required Features
score: 5
max: 5
passed: true
comment: Has entrance, exit, S marker, G marker, guaranteed single solution
via DFS
- id: SC-04
name: Data Range
score: 3
max: 3
passed: true
comment: 25x25 grid as specified, properly bounded
- id: SC-05
name: Legend Accuracy
score: 2
max: 2
passed: true
comment: N/A for maze; S and G markers serve as legend
- id: SC-06
name: Title Format
score: 2
max: 2
passed: true
comment: 'Correct format: maze-printable · pygal · pyplots.ai'
data_quality:
score: 17
max: 20
items:
- id: DQ-01
name: Feature Coverage
score: 7
max: 8
passed: true
comment: Shows maze with entrance, exit, start/goal markers, solvable paths;
slight deduction for marker visibility
- id: DQ-02
name: Realistic Context
score: 7
max: 7
passed: true
comment: Printable maze puzzle is a real, neutral application per spec
- id: DQ-03
name: Appropriate Scale
score: 3
max: 5
passed: true
comment: 25x25 is within recommended range; passages are slightly tight for
comfortable pen marking
code_quality:
score: 9
max: 10
items:
- id: CQ-01
name: KISS Structure
score: 3
max: 3
passed: true
comment: Linear script without functions/classes
- id: CQ-02
name: Reproducibility
score: 3
max: 3
passed: true
comment: Uses np.random.seed(42)
- id: CQ-03
name: Clean Imports
score: 1
max: 2
passed: true
comment: os import used only for cleanup; minor redundancy
- id: CQ-04
name: No Deprecated API
score: 1
max: 1
passed: true
comment: Uses current pygal API
- id: CQ-05
name: Output Correct
score: 1
max: 1
passed: true
comment: Saves as plot.png and plot.html
library_features:
score: 3
max: 5
items:
- id: LF-01
name: Distinctive Features
score: 3
max: 5
passed: true
comment: Creative use of Dot chart for maze visualization; SVG manipulation
for markers shows advanced usage
verdict: APPROVED
impl_tags:
dependencies:
- cairosvg
techniques:
- annotations
- html-export
patterns:
- data-generation
- matrix-construction
- iteration-over-groups
dataprep: []
styling:
- minimal-chrome