{"spec_id":"sudoku-basic","library":"pygal","language":"python","code":"\"\"\" anyplot.ai\nsudoku-basic: Basic Sudoku Grid\nLibrary: pygal 3.1.3 | Python 3.13.14\nQuality: 88/100 | Updated: 2026-06-25\n\"\"\"\n\nimport os\nimport sys\nimport xml.etree.ElementTree as ET\n\n\n# Drop script dir from sys.path so `import pygal` resolves to the library, not this file\nsys.path[:] = [p for p in sys.path if p not in (\"\", \".\", os.path.dirname(os.path.abspath(__file__)))]\n\nimport cairosvg\nimport pygal\nfrom pygal.style import Style\n\n\n# Theme tokens\nTHEME = os.getenv(\"ANYPLOT_THEME\", \"light\")\nPAGE_BG = \"#FAF8F1\" if THEME == \"light\" else \"#1A1A17\"\nELEVATED_BG = \"#FFFDF6\" if THEME == \"light\" else \"#242420\"\nINK = \"#1A1A17\" if THEME == \"light\" else \"#F0EFE8\"\nINK_SOFT = \"#4A4A44\" if THEME == \"light\" else \"#B8B7B0\"\nINK_MUTED = \"#6B6A63\" if THEME == \"light\" else \"#A8A79F\"\n\n# Data: classic Sudoku puzzle (0 = empty cell)\ngrid = [\n    [5, 3, 0, 0, 7, 0, 0, 0, 0],\n    [6, 0, 0, 1, 9, 5, 0, 0, 0],\n    [0, 9, 8, 0, 0, 0, 0, 6, 0],\n    [8, 0, 0, 0, 6, 0, 0, 0, 3],\n    [4, 0, 0, 8, 0, 3, 0, 0, 1],\n    [7, 0, 0, 0, 2, 0, 0, 0, 6],\n    [0, 6, 0, 0, 0, 0, 2, 8, 0],\n    [0, 0, 0, 4, 1, 9, 0, 0, 5],\n    [0, 0, 0, 0, 8, 0, 0, 7, 9],\n]\n\n# Title with required language token; 42 chars < 67-char baseline → font stays at default 66\ntitle = \"sudoku-basic · python · pygal · anyplot.ai\"\ntitle_font_size = 66\n\ncustom_style = Style(\n    background=PAGE_BG,\n    plot_background=PAGE_BG,\n    foreground=INK,\n    foreground_strong=INK,\n    foreground_subtle=INK_MUTED,\n    colors=(INK,),  # monochrome — spec requires clean black-and-white design\n    title_font_size=title_font_size,\n    label_font_size=56,\n    major_label_font_size=44,\n    legend_font_size=44,\n    value_font_size=36,\n    font_family=\"DejaVu Sans, Arial, sans-serif\",\n)\n\n# Square canvas — canonical 2400×2400 for symmetric grid layouts\nCANVAS = 2400\n\nchart = pygal.Bar(\n    width=CANVAS,\n    height=CANVAS,\n    style=custom_style,\n    title=title,\n    show_legend=False,\n    show_x_labels=False,\n    show_y_labels=False,\n    show_x_guides=False,\n    show_y_guides=False,\n    margin=50,\n    margin_top=150,\n    no_data_text=\"\",\n)\n\n# No data added — pygal renders title + background; render_tree() gives us the SVG to extend\nsvg_root = chart.render_tree()\n\n# Clear any pygal plot/overlay groups (no-data placeholder, empty chart frame)\nSVG_NS = \"{http://www.w3.org/2000/svg}\"\nfor plot_group in list(svg_root.iter(f\"{SVG_NS}g\")):\n    cls = plot_group.attrib.get(\"class\", \"\")\n    if cls.startswith(\"plot\"):\n        plot_group.clear()\n        plot_group.set(\"class\", cls)\n\n# Sudoku geometry — centered below the title with generous margins\nTOP = 230\nBOTTOM = 110\nSIDE = 190\nGRID_SIZE = min(CANVAS - 2 * SIDE, CANVAS - TOP - BOTTOM)\nCELL = GRID_SIZE / 9\nGRID_X = (CANVAS - GRID_SIZE) / 2\nGRID_Y = TOP + (CANVAS - TOP - BOTTOM - GRID_SIZE) / 2\n\nsudoku = ET.SubElement(svg_root, \"g\", {\"class\": \"sudoku\"})\n\n# Base grid background\nET.SubElement(\n    sudoku,\n    \"rect\",\n    {\"x\": str(GRID_X), \"y\": str(GRID_Y), \"width\": str(GRID_SIZE), \"height\": str(GRID_SIZE), \"fill\": PAGE_BG},\n)\n\n# Alternating 3×3 box fills — checkerboard elevation reinforces regional grouping (DE-03)\nfor box_row in range(3):\n    for box_col in range(3):\n        if (box_row + box_col) % 2 == 0:\n            bx = GRID_X + box_col * CELL * 3\n            by = GRID_Y + box_row * CELL * 3\n            ET.SubElement(\n                sudoku,\n                \"rect\",\n                {\"x\": str(bx), \"y\": str(by), \"width\": str(CELL * 3), \"height\": str(CELL * 3), \"fill\": ELEVATED_BG},\n            )\n\n# Thin lines — individual cell boundaries (skip 3×3 box positions)\nfor i in range(1, 9):\n    if i % 3 == 0:\n        continue\n    x = GRID_X + i * CELL\n    y = GRID_Y + i * CELL\n    ET.SubElement(\n        sudoku,\n        \"line\",\n        {\n            \"x1\": str(x),\n            \"y1\": str(GRID_Y),\n            \"x2\": str(x),\n            \"y2\": str(GRID_Y + GRID_SIZE),\n            \"stroke\": INK_SOFT,\n            \"stroke-width\": \"2\",\n        },\n    )\n    ET.SubElement(\n        sudoku,\n        \"line\",\n        {\n            \"x1\": str(GRID_X),\n            \"y1\": str(y),\n            \"x2\": str(GRID_X + GRID_SIZE),\n            \"y2\": str(y),\n            \"stroke\": INK_SOFT,\n            \"stroke-width\": \"2\",\n        },\n    )\n\n# Medium lines — internal 3×3 box dividers (10px)\nfor i in [3, 6]:\n    x = GRID_X + i * CELL\n    y = GRID_Y + i * CELL\n    ET.SubElement(\n        sudoku,\n        \"line\",\n        {\n            \"x1\": str(x),\n            \"y1\": str(GRID_Y),\n            \"x2\": str(x),\n            \"y2\": str(GRID_Y + GRID_SIZE),\n            \"stroke\": INK,\n            \"stroke-width\": \"10\",\n            \"stroke-linecap\": \"square\",\n        },\n    )\n    ET.SubElement(\n        sudoku,\n        \"line\",\n        {\n            \"x1\": str(GRID_X),\n            \"y1\": str(y),\n            \"x2\": str(GRID_X + GRID_SIZE),\n            \"y2\": str(y),\n            \"stroke\": INK,\n            \"stroke-width\": \"10\",\n            \"stroke-linecap\": \"square\",\n        },\n    )\n\n# Heavy outer border (16px) — three-level weight hierarchy: cell(2) / box(10) / border(16)\nET.SubElement(\n    sudoku,\n    \"rect\",\n    {\n        \"x\": str(GRID_X),\n        \"y\": str(GRID_Y),\n        \"width\": str(GRID_SIZE),\n        \"height\": str(GRID_SIZE),\n        \"fill\": \"none\",\n        \"stroke\": INK,\n        \"stroke-width\": \"16\",\n    },\n)\n\n# Numbers — monochrome ink, bold, centered within cells (dominant-baseline for precise alignment)\nfont_size = int(CELL * 0.55)\nfor row in range(9):\n    for col in range(9):\n        value = grid[row][col]\n        if value == 0:\n            continue\n        cx = GRID_X + col * CELL + CELL / 2\n        cy = GRID_Y + row * CELL + CELL / 2\n        text_node = ET.SubElement(\n            sudoku,\n            \"text\",\n            {\n                \"x\": str(cx),\n                \"y\": str(cy),\n                \"text-anchor\": \"middle\",\n                \"dominant-baseline\": \"central\",\n                \"fill\": INK,\n                \"style\": f\"font-size:{font_size}px;font-weight:bold;font-family:DejaVu Sans,Arial,sans-serif;\",\n            },\n        )\n        text_node.text = str(value)\n\n# Save\nsvg_bytes = ET.tostring(svg_root, xml_declaration=True, encoding=\"utf-8\")\ncairosvg.svg2png(bytestring=svg_bytes, write_to=f\"plot-{THEME}.png\", output_width=CANVAS, output_height=CANVAS)\n\nhtml_page = (\n    f'<!DOCTYPE html><html><head><meta charset=\"utf-8\">'\n    f\"<title>sudoku-basic · python · pygal · anyplot.ai</title></head>\"\n    f'<body style=\"margin:0;background:{PAGE_BG};display:flex;'\n    f'justify-content:center;align-items:center;min-height:100vh;\">'\n    f\"{svg_bytes.decode('utf-8')}\"\n    f\"</body></html>\"\n)\nwith open(f\"plot-{THEME}.html\", \"w\", encoding=\"utf-8\") as f:\n    f.write(html_page)\n"}