Crossword Puzzle Grid — Makie.jl

A crossword puzzle grid visualization with white entry cells for letters, black blocking cells, and numbered starting positions for across and down words. The symmetric black cell pattern follows traditional newspaper-style crossword conventions, creating a clean and recognizable puzzle layout suitable for printing or digital display.

Crossword Puzzle Grid rendered with Makie.jl

Renders

Julia source (Makie.jl)

# anyplot.ai
# crossword-basic: Crossword Puzzle Grid
# Library: makie 0.21.9 | Julia 1.11.9
# Quality: 85/100 | Updated: 2026-09-02

using CairoMakie
using Colors

# --- Theme tokens -------------------------------------------------------------
const THEME    = get(ENV, "ANYPLOT_THEME", "light")
const PAGE_BG  = THEME == "light" ? colorant"#FAF8F1" : colorant"#1A1A17"
const INK      = THEME == "light" ? colorant"#1A1A17" : colorant"#F0EFE8"
const INK_SOFT = THEME == "light" ? colorant"#4A4A44" : colorant"#B8B7B0"

# Fixed, theme-invariant data-encoding colors for the grid itself. The
# black/white cell pattern IS the plot's data (newspaper-crossword
# convention), so it must render identically in both themes — unlike the
# chrome tokens above, which are meant to flip.
const CELL_BLOCKED = colorant"#1A1A17"
const CELL_ENTRY   = colorant"#FFFDF6"

# --- Data ----------------------------------------------------------------------
# 15x15 grid; blocking cells carry traditional 180-degree rotational symmetry.
# Seeding only the upper half and mirroring each seed guarantees the symmetry
# by construction rather than by manually checking a hand-typed matrix.
const N = 15

seed_blocks = [
    (1, 4), (1, 11), (2, 4), (2, 11), (3, 4), (3, 8), (3, 11),
    (4, 1), (4, 2), (4, 7), (5, 5), (5, 10), (6, 3), (6, 6),
    (7, 7), (8, 1), (8, 4),
]

blocked = falses(N, N)
for (r, c) in seed_blocks
    blocked[r, c] = true
    blocked[N + 1 - r, N + 1 - c] = true
end

# Word-start numbering: a cell is numbered when it opens an across and/or a
# down entry, scanning left-to-right then top-to-bottom (standard convention).
numbers = zeros(Int, N, N)
counter = Ref(1)
n_across = Ref(0)
n_down = Ref(0)
for r in 1:N, c in 1:N
    blocked[r, c] && continue
    starts_across = (c == 1 || blocked[r, c - 1]) && (c < N && !blocked[r, c + 1])
    starts_down = (r == 1 || blocked[r - 1, c]) && (r < N && !blocked[r + 1, c])
    starts_across && (n_across[] += 1)
    starts_down && (n_down[] += 1)
    if starts_across || starts_down
        numbers[r, c] = counter[]
        counter[] += 1
    end
end

# --- Plot ------------------------------------------------------------------
fig = Figure(
    resolution = (1200, 1200),
    fontsize = 14,
    backgroundcolor = PAGE_BG,
)

ax = Axis(
    fig[1, 1];
    title = "crossword-basic · julia · makie · anyplot.ai",
    titlesize = 32,
    titlecolor = INK,
    aspect = DataAspect(),
    backgroundcolor = PAGE_BG,
)
hidedecorations!(ax)
hidespines!(ax)
xlims!(ax, -0.4, N + 0.4)
ylims!(ax, -0.4, N + 0.4)

# Entry cells (white, for letters) vs. blocking cells (black) — this is the
# plot's data, so both colors are fixed constants that never flip with
# ANYPLOT_THEME (only the surrounding chrome does).
cell_rects = [Rect2f(Float32(c - 1), Float32(N - r), 1.0f0, 1.0f0) for r in 1:N for c in 1:N]
cell_colors = [blocked[r, c] ? CELL_BLOCKED : CELL_ENTRY for r in 1:N for c in 1:N]
poly!(ax, cell_rects; color = cell_colors, strokewidth = 0)

# Grid lines separating every cell
for i in 0:N
    linesegments!(ax, [Point2f(0, i), Point2f(N, i)]; color = INK_SOFT, linewidth = 1.5)
    linesegments!(ax, [Point2f(i, 0), Point2f(i, N)]; color = INK_SOFT, linewidth = 1.5)
end

# Outer frame: a heavier ink rule plus a thin inset accent ruled entirely in
# the surrounding margin (never crossing live cells) gives the grid the
# double-ruled "matted" border common to printed puzzle books, instead of a
# single flat outline.
lines!(
    ax,
    [Point2f(0, 0), Point2f(N, 0), Point2f(N, N), Point2f(0, N), Point2f(0, 0)];
    color = INK,
    linewidth = 4,
)
accent = 0.18
lines!(
    ax,
    [
        Point2f(-accent, -accent), Point2f(N + accent, -accent),
        Point2f(N + accent, N + accent), Point2f(-accent, N + accent),
        Point2f(-accent, -accent),
    ];
    color = INK_SOFT,
    linewidth = 1.5,
)

# Clue-start numbers, top-left corner of each entry cell — sized up and
# bolded so they stay legible on a 2400x2400 canvas at mobile widths.
for r in 1:N, c in 1:N
    numbers[r, c] == 0 && continue
    x = c - 1
    y = N - r
    text!(
        ax,
        x + 0.09,
        y + 1 - 0.08;
        text = string(numbers[r, c]),
        fontsize = 19,
        font = :bold,
        color = INK_SOFT,
        align = (:left, :top),
    )
end

# Subtitle: across/down entry counts add a touch of data storytelling below
# the grid via a second Figure row (Label), beyond the plain grid alone. The
# row needs an explicit fixed height — otherwise the DataAspect()-constrained
# Axis above claims the whole figure and squeezes this row to invisibility.
Label(
    fig[2, 1],
    "$(N) × $(N) grid · $(n_across[]) across · $(n_down[]) down";
    fontsize = 15,
    color = INK_SOFT,
    tellwidth = false,
    padding = (0, 0, 0, 18),
)
rowsize!(fig.layout, 2, Fixed(60))

# --- Save -----------------------------------------------------------------
save("plot-$(THEME).png", fig; px_per_unit = 2)

Retrieve this implementation

Runnable source as JSON, for any HTTP client: https://api.anyplot.ai/specs/crossword-basic/makie/code. Any spec id and library id listed in llms-full.txt fit the same URL shape; every URL below is complete and callable.

{
  "spec_id": "crossword-basic",
  "language": "julia",
  "library": "makie",
  "page": "https://anyplot.ai/crossword-basic/julia/makie",
  "hub": "https://anyplot.ai/crossword-basic",
  "code_json": "https://api.anyplot.ai/specs/crossword-basic/makie/code",
  "spec_json": "https://api.anyplot.ai/specs/crossword-basic",
  "render_light_png": "https://storage.googleapis.com/anyplot-images/plots/crossword-basic/julia/makie/plot-light.png",
  "render_dark_png": "https://storage.googleapis.com/anyplot-images/plots/crossword-basic/julia/makie/plot-dark.png",
  "quality_score": 85.0,
  "license": "MIT",
  "guide": "https://anyplot.ai/llms.txt"
}

Part of Crossword Puzzle Grid on anyplot.ai.

Other implementations