{"spec_id":"chessboard-pieces","library":"highcharts","language":"javascript","code":"// anyplot.ai\n// chessboard-pieces: Chess Board with Pieces for Position Diagrams\n// Library: highcharts 12.6.0 | JavaScript 22.23.2\n// Quality: 92/100 | Created: 2026-09-01\n//# anyplot-orientation: square\n\nconst t = window.ANYPLOT_TOKENS;\n\n// --- Data (in-memory, deterministic) ----------------------------------------\n// Scholar's mate, final position after 1.e4 e5 2.Bc4 Nc6 3.Qh5 Nf6?? 4.Qxf7#\n// Square -> piece code (uppercase = white, lowercase = black).\nconst pieces = {\n  e1: \"K\", a1: \"R\", h1: \"R\", c1: \"B\", b1: \"N\", g1: \"N\",\n  f7: \"Q\", c4: \"B\", a2: \"P\", b2: \"P\", c2: \"P\", d2: \"P\",\n  e4: \"P\", f2: \"P\", g2: \"P\", h2: \"P\",\n  e8: \"k\", a8: \"r\", h8: \"r\", c8: \"b\", f8: \"b\", c6: \"n\", f6: \"n\",\n  d8: \"q\", a7: \"p\", b7: \"p\", c7: \"p\", d7: \"p\", e5: \"p\", g7: \"p\", h7: \"p\",\n};\n\n// Unicode chess symbols: hollow glyphs U+2654-2659 read as white pieces,\n// filled glyphs U+265A-265F read as black pieces — no extra data color needed.\nconst GLYPHS = {\n  K: \"♔\", Q: \"♕\", R: \"♖\", B: \"♗\", N: \"♘\", P: \"♙\",\n  k: \"♚\", q: \"♛\", r: \"♜\", b: \"♝\", n: \"♞\", p: \"♟\",\n};\n\nconst FILES = [\"a\", \"b\", \"c\", \"d\", \"e\", \"f\", \"g\", \"h\"];\n\n// --- Chart -------------------------------------------------------------------\nconst title = \"chessboard-pieces · javascript · highcharts · anyplot.ai\";\nconst titleFontSize = Math.round(22 * (title.length > 67 ? 67 / title.length : 1));\n\nconst chart = Highcharts.chart(\"container\", {\n  chart: { backgroundColor: \"transparent\", animation: false,\n           style: { fontFamily: \"inherit\" } },\n  credits: { enabled: false },\n  title: { text: title, style: { color: t.ink, fontSize: `${titleFontSize}px`, fontWeight: \"600\" } },\n  subtitle: { text: \"Scholar’s mate · final position after 4.Qxf7#\",\n              style: { color: t.inkSoft, fontSize: \"14px\" } },\n  xAxis: { visible: false },\n  yAxis: { visible: false, title: { text: null } },\n  legend: { enabled: false },\n  tooltip: { enabled: false },\n  plotOptions: { series: { animation: false } },\n  series: [],\n});\n\n// --- Board geometry (derived from the rendered plot area, then drawn with the\n// core SVGRenderer — the heatmap module isn't loaded, so the 8x8 grid and the\n// piece glyphs are custom shapes rather than a series type) -------------------\nconst GUTTER = 36; // reserves room for the file/rank coordinate labels\nconst availW = chart.plotWidth - GUTTER;\nconst availH = chart.plotHeight - GUTTER;\nconst boardSize = Math.min(availW, availH);\nconst boardLeft = chart.plotLeft + GUTTER + (availW - boardSize) / 2;\nconst boardTop = chart.plotTop + (availH - boardSize) / 2;\nconst cell = boardSize / 8;\n\nconst lightSquareFill = t.elevatedBg;\nconst darkSquareFill = Highcharts.color(t.ink).setOpacity(0.16).get();\nconst labelStyle = { color: t.inkSoft, fontSize: \"14px\" };\n\n// Board frame\nchart.renderer.rect(boardLeft, boardTop, boardSize, boardSize, 0)\n  .attr({ fill: \"none\", stroke: t.inkSoft, \"stroke-width\": 1.5 })\n  .add();\n\nfor (let displayRow = 0; displayRow < 8; displayRow++) {\n  const rank = 8 - displayRow; // row 0 (top) is rank 8, row 7 (bottom) is rank 1\n\n  for (let file = 0; file < 8; file++) {\n    const rankIndex = rank - 1;\n    const isLight = (file + rankIndex) % 2 === 1; // a1 dark, h1 light\n    const x = boardLeft + file * cell;\n    const y = boardTop + displayRow * cell;\n\n    chart.renderer.rect(x, y, cell, cell)\n      .attr({ fill: isLight ? lightSquareFill : darkSquareFill })\n      .add();\n\n    const square = `${FILES[file]}${rank}`;\n    const piece = pieces[square];\n    if (piece) {\n      chart.renderer.text(GLYPHS[piece], x + cell / 2, y + cell / 2 + cell * 0.32)\n        .attr({ align: \"center\", zIndex: 5 })\n        .css({ color: t.ink, fontSize: `${Math.round(cell * 0.62)}px`,\n               textOutline: `2px ${t.pageBg}` })\n        .add();\n    }\n  }\n\n  // Rank coordinate label (left gutter)\n  chart.renderer.text(String(rank), chart.plotLeft + GUTTER / 2, boardTop + displayRow * cell + cell / 2 + 5)\n    .attr({ align: \"center\" })\n    .css(labelStyle)\n    .add();\n}\n\n// File coordinate labels (bottom gutter)\nFILES.forEach((file, i) => {\n  chart.renderer.text(file, boardLeft + i * cell + cell / 2, boardTop + boardSize + GUTTER / 2 + 5)\n    .attr({ align: \"center\" })\n    .css(labelStyle)\n    .add();\n});\n"}