Crossword Puzzle Grid — Highcharts

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 Highcharts

Renders

JavaScript source (Highcharts)

// anyplot.ai
// crossword-basic: Crossword Puzzle Grid
// Library: highcharts 12.6.0 | JavaScript 22.23.2
// Quality: 84/100 | Created: 2026-09-02

//# anyplot-orientation: square
const t = window.ANYPLOT_TOKENS;
const size = window.ANYPLOT_SIZE;

// Crossword cell colors — fixed regardless of theme (contrast IS the data):
// blocked squares stay near-black and entry squares stay near-white in both
// light and dark renders, matching the traditional newspaper-crossword
// convention instead of flipping with the page chrome.
const CELL_BLOCK = "#1A1A17";
const CELL_ENTRY = "#FAF8F1";

// --- Data: symmetric 15x15 crossword grid (180-degree rotational symmetry) -
const N = 15;

// Only the "first half" of blocked cells is listed; the mirrored partner
// (N-1-row, N-1-col) is added below, which is how newspaper crosswords keep
// their traditional 180-degree rotational symmetry.
const blockedHalf = [
  [0, 3], [0, 10],
  [1, 3], [1, 10],
  [2, 6], [2, 8],
  [3, 0], [3, 6], [3, 11],
  [4, 4], [4, 9],
  [5, 2], [5, 6], [5, 8], [5, 12],
  [6, 0], [6, 5], [6, 9], [6, 14],
  [7, 7],
];
const blocked = new Set();
blockedHalf.forEach(([row, col]) => {
  blocked.add(`${row},${col}`);
  blocked.add(`${N - 1 - row},${N - 1 - col}`);
});
const isBlocked = (row, col) => blocked.has(`${row},${col}`);

// Standard crossword numbering: a cell is numbered when it starts an across
// and/or a down entry (no open cell to its left/above, and an open cell
// follows to its right/below).
let nextNumber = 1;
const numberedCells = [];
for (let row = 0; row < N; row += 1) {
  for (let col = 0; col < N; col += 1) {
    if (isBlocked(row, col)) continue;
    const startsAcross = (col === 0 || isBlocked(row, col - 1)) &&
      col + 1 < N && !isBlocked(row, col + 1);
    const startsDown = (row === 0 || isBlocked(row - 1, col)) &&
      row + 1 < N && !isBlocked(row + 1, col);
    if (startsAcross || startsDown) {
      numberedCells.push({ row, col, number: nextNumber });
      nextNumber += 1;
    }
  }
}

// --- Layout: fixed margins keep the plot area an exact square so grid cells
// stay 1:1, independent of Highcharts' auto layout for title/axes -----------
const MARGIN_TOP = 100;
const MARGIN_BOTTOM = 40;
const MARGIN_SIDE = 70;
const cellPx = (size.width - MARGIN_SIDE * 2) / N;

const blockedCells = [...blocked].map((key) => {
  const [row, col] = key.split(",").map(Number);
  return { x: col + 0.5, y: row + 0.5 };
});

const entryCells = [];
for (let row = 0; row < N; row += 1) {
  for (let col = 0; col < N; col += 1) {
    if (!isBlocked(row, col)) entryCells.push({ x: col + 0.5, y: row + 0.5 });
  }
}

const numberPoints = numberedCells.map(({ row, col, number }) => ({
  x: col,
  y: row,
  name: String(number),
}));

// --- Chart -------------------------------------------------------------
Highcharts.chart("container", {
  chart: {
    type: "scatter",
    backgroundColor: "transparent",
    animation: false,
    spacing: [0, 0, 0, 0],
    marginTop: MARGIN_TOP,
    marginBottom: MARGIN_BOTTOM,
    marginLeft: MARGIN_SIDE,
    marginRight: MARGIN_SIDE,
    style: { fontFamily: "inherit" },
  },
  credits: { enabled: false },
  colors: t.palette,
  title: {
    text: "crossword-basic · javascript · highcharts · anyplot.ai",
    style: { color: t.ink, fontSize: "22px", fontWeight: "600" },
  },
  tooltip: { enabled: false },
  legend: { enabled: false },
  xAxis: {
    min: 0,
    max: N,
    tickInterval: 1,
    gridLineWidth: 1,
    gridLineColor: t.grid,
    lineWidth: 0,
    tickLength: 0,
    labels: { enabled: false },
    title: { text: null },
  },
  yAxis: {
    min: 0,
    max: N,
    reversed: true,
    tickInterval: 1,
    gridLineWidth: 1,
    gridLineColor: t.grid,
    lineWidth: 0,
    tickLength: 0,
    labels: { enabled: false },
    title: { text: null },
  },
  plotOptions: {
    series: { animation: false, enableMouseTracking: false },
  },
  series: [
    {
      name: "Entry cells",
      type: "scatter",
      data: entryCells,
      marker: {
        symbol: "square",
        radius: cellPx / 2 + 0.5,
        fillColor: CELL_ENTRY,
        lineWidth: 0,
      },
      dataLabels: { enabled: false },
    },
    {
      name: "Blocked cells",
      type: "scatter",
      data: blockedCells,
      marker: {
        symbol: "square",
        radius: cellPx / 2 + 0.5,
        fillColor: CELL_BLOCK,
        lineWidth: 0,
      },
      dataLabels: { enabled: false },
    },
    {
      name: "Clue numbers",
      type: "scatter",
      data: numberPoints,
      marker: { enabled: false },
      dataLabels: {
        enabled: true,
        format: "{point.name}",
        align: "left",
        verticalAlign: "top",
        x: 3,
        y: 2,
        padding: 0,
        crop: false,
        overflow: "allow",
        allowOverlap: true,
        style: {
          color: CELL_BLOCK,
          fontSize: "14px",
          fontWeight: "600",
          textOutline: "none",
        },
      },
    },
  ],
});

Retrieve this implementation

Runnable source as JSON, for any HTTP client: https://api.anyplot.ai/specs/crossword-basic/highcharts/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": "javascript",
  "library": "highcharts",
  "page": "https://anyplot.ai/crossword-basic/javascript/highcharts",
  "hub": "https://anyplot.ai/crossword-basic",
  "code_json": "https://api.anyplot.ai/specs/crossword-basic/highcharts/code",
  "spec_json": "https://api.anyplot.ai/specs/crossword-basic",
  "render_light_png": "https://storage.googleapis.com/anyplot-images/plots/crossword-basic/javascript/highcharts/plot-light.png",
  "render_dark_png": "https://storage.googleapis.com/anyplot-images/plots/crossword-basic/javascript/highcharts/plot-dark.png",
  "interactive_light_html": "https://storage.googleapis.com/anyplot-images/plots/crossword-basic/javascript/highcharts/plot-light.html",
  "interactive_dark_html": "https://storage.googleapis.com/anyplot-images/plots/crossword-basic/javascript/highcharts/plot-dark.html",
  "quality_score": 84.0,
  "license": "MIT",
  "guide": "https://anyplot.ai/llms.txt"
}

Part of Crossword Puzzle Grid on anyplot.ai.

Other implementations