Basic Sudoku Grid — Pygal

A standard 9×9 Sudoku grid with proper visual hierarchy showing 3×3 box regions. Thick lines separate the nine boxes while thin lines divide individual cells. The clean black-and-white design is optimized for printing and solving puzzles.

Basic Sudoku Grid rendered with Pygal

Python source (Pygal)

""" anyplot.ai
sudoku-basic: Basic Sudoku Grid
Library: pygal 3.1.3 | Python 3.13.14
Quality: 88/100 | Updated: 2026-06-25
"""

import os
import sys
import xml.etree.ElementTree as ET


# Drop script dir from sys.path so `import pygal` resolves to the library, not this file
sys.path[:] = [p for p in sys.path if p not in ("", ".", os.path.dirname(os.path.abspath(__file__)))]

import cairosvg
import pygal
from pygal.style import Style


# Theme tokens
THEME = os.getenv("ANYPLOT_THEME", "light")
PAGE_BG = "#FAF8F1" if THEME == "light" else "#1A1A17"
ELEVATED_BG = "#FFFDF6" if THEME == "light" else "#242420"
INK = "#1A1A17" if THEME == "light" else "#F0EFE8"
INK_SOFT = "#4A4A44" if THEME == "light" else "#B8B7B0"
INK_MUTED = "#6B6A63" if THEME == "light" else "#A8A79F"

# Data: classic Sudoku puzzle (0 = empty cell)
grid = [
    [5, 3, 0, 0, 7, 0, 0, 0, 0],
    [6, 0, 0, 1, 9, 5, 0, 0, 0],
    [0, 9, 8, 0, 0, 0, 0, 6, 0],
    [8, 0, 0, 0, 6, 0, 0, 0, 3],
    [4, 0, 0, 8, 0, 3, 0, 0, 1],
    [7, 0, 0, 0, 2, 0, 0, 0, 6],
    [0, 6, 0, 0, 0, 0, 2, 8, 0],
    [0, 0, 0, 4, 1, 9, 0, 0, 5],
    [0, 0, 0, 0, 8, 0, 0, 7, 9],
]

# Title with required language token; 42 chars < 67-char baseline → font stays at default 66
title = "sudoku-basic · python · pygal · anyplot.ai"
title_font_size = 66

custom_style = Style(
    background=PAGE_BG,
    plot_background=PAGE_BG,
    foreground=INK,
    foreground_strong=INK,
    foreground_subtle=INK_MUTED,
    colors=(INK,),  # monochrome — spec requires clean black-and-white design
    title_font_size=title_font_size,
    label_font_size=56,
    major_label_font_size=44,
    legend_font_size=44,
    value_font_size=36,
    font_family="DejaVu Sans, Arial, sans-serif",
)

# Square canvas — canonical 2400×2400 for symmetric grid layouts
CANVAS = 2400

chart = pygal.Bar(
    width=CANVAS,
    height=CANVAS,
    style=custom_style,
    title=title,
    show_legend=False,
    show_x_labels=False,
    show_y_labels=False,
    show_x_guides=False,
    show_y_guides=False,
    margin=50,
    margin_top=150,
    no_data_text="",
)

# No data added — pygal renders title + background; render_tree() gives us the SVG to extend
svg_root = chart.render_tree()

# Clear any pygal plot/overlay groups (no-data placeholder, empty chart frame)
SVG_NS = "{http://www.w3.org/2000/svg}"
for plot_group in list(svg_root.iter(f"{SVG_NS}g")):
    cls = plot_group.attrib.get("class", "")
    if cls.startswith("plot"):
        plot_group.clear()
        plot_group.set("class", cls)

# Sudoku geometry — centered below the title with generous margins
TOP = 230
BOTTOM = 110
SIDE = 190
GRID_SIZE = min(CANVAS - 2 * SIDE, CANVAS - TOP - BOTTOM)
CELL = GRID_SIZE / 9
GRID_X = (CANVAS - GRID_SIZE) / 2
GRID_Y = TOP + (CANVAS - TOP - BOTTOM - GRID_SIZE) / 2

sudoku = ET.SubElement(svg_root, "g", {"class": "sudoku"})

# Base grid background
ET.SubElement(
    sudoku,
    "rect",
    {"x": str(GRID_X), "y": str(GRID_Y), "width": str(GRID_SIZE), "height": str(GRID_SIZE), "fill": PAGE_BG},
)

# Alternating 3×3 box fills — checkerboard elevation reinforces regional grouping (DE-03)
for box_row in range(3):
    for box_col in range(3):
        if (box_row + box_col) % 2 == 0:
            bx = GRID_X + box_col * CELL * 3
            by = GRID_Y + box_row * CELL * 3
            ET.SubElement(
                sudoku,
                "rect",
                {"x": str(bx), "y": str(by), "width": str(CELL * 3), "height": str(CELL * 3), "fill": ELEVATED_BG},
            )

# Thin lines — individual cell boundaries (skip 3×3 box positions)
for i in range(1, 9):
    if i % 3 == 0:
        continue
    x = GRID_X + i * CELL
    y = GRID_Y + i * CELL
    ET.SubElement(
        sudoku,
        "line",
        {
            "x1": str(x),
            "y1": str(GRID_Y),
            "x2": str(x),
            "y2": str(GRID_Y + GRID_SIZE),
            "stroke": INK_SOFT,
            "stroke-width": "2",
        },
    )
    ET.SubElement(
        sudoku,
        "line",
        {
            "x1": str(GRID_X),
            "y1": str(y),
            "x2": str(GRID_X + GRID_SIZE),
            "y2": str(y),
            "stroke": INK_SOFT,
            "stroke-width": "2",
        },
    )

# Medium lines — internal 3×3 box dividers (10px)
for i in [3, 6]:
    x = GRID_X + i * CELL
    y = GRID_Y + i * CELL
    ET.SubElement(
        sudoku,
        "line",
        {
            "x1": str(x),
            "y1": str(GRID_Y),
            "x2": str(x),
            "y2": str(GRID_Y + GRID_SIZE),
            "stroke": INK,
            "stroke-width": "10",
            "stroke-linecap": "square",
        },
    )
    ET.SubElement(
        sudoku,
        "line",
        {
            "x1": str(GRID_X),
            "y1": str(y),
            "x2": str(GRID_X + GRID_SIZE),
            "y2": str(y),
            "stroke": INK,
            "stroke-width": "10",
            "stroke-linecap": "square",
        },
    )

# Heavy outer border (16px) — three-level weight hierarchy: cell(2) / box(10) / border(16)
ET.SubElement(
    sudoku,
    "rect",
    {
        "x": str(GRID_X),
        "y": str(GRID_Y),
        "width": str(GRID_SIZE),
        "height": str(GRID_SIZE),
        "fill": "none",
        "stroke": INK,
        "stroke-width": "16",
    },
)

# Numbers — monochrome ink, bold, centered within cells (dominant-baseline for precise alignment)
font_size = int(CELL * 0.55)
for row in range(9):
    for col in range(9):
        value = grid[row][col]
        if value == 0:
            continue
        cx = GRID_X + col * CELL + CELL / 2
        cy = GRID_Y + row * CELL + CELL / 2
        text_node = ET.SubElement(
            sudoku,
            "text",
            {
                "x": str(cx),
                "y": str(cy),
                "text-anchor": "middle",
                "dominant-baseline": "central",
                "fill": INK,
                "style": f"font-size:{font_size}px;font-weight:bold;font-family:DejaVu Sans,Arial,sans-serif;",
            },
        )
        text_node.text = str(value)

# Save
svg_bytes = ET.tostring(svg_root, xml_declaration=True, encoding="utf-8")
cairosvg.svg2png(bytestring=svg_bytes, write_to=f"plot-{THEME}.png", output_width=CANVAS, output_height=CANVAS)

html_page = (
    f'<!DOCTYPE html><html><head><meta charset="utf-8">'
    f"<title>sudoku-basic · python · pygal · anyplot.ai</title></head>"
    f'<body style="margin:0;background:{PAGE_BG};display:flex;'
    f'justify-content:center;align-items:center;min-height:100vh;">'
    f"{svg_bytes.decode('utf-8')}"
    f"</body></html>"
)
with open(f"plot-{THEME}.html", "w", encoding="utf-8") as f:
    f.write(html_page)

Part of Basic Sudoku Grid on anyplot.ai.

Other implementations