Chess Board with Pieces for Position Diagrams — D3.js

A chess board visualization with pieces that can be positioned programmatically to display specific game positions, puzzles, or notable games. Pieces are defined using a dictionary mapping squares (e.g., 'e4': 'K') to standard chess notation where uppercase letters represent white pieces (K/Q/R/B/N/P) and lowercase represent black pieces (k/q/r/b/n/p). Unicode chess symbols provide clean, recognizable piece rendering.

Chess Board with Pieces for Position Diagrams rendered with D3.js

Renders

JavaScript source (D3.js)

// anyplot.ai
// chessboard-pieces: Chess Board with Pieces for Position Diagrams
// Library: d3 7.9.0 | JavaScript 22.23.2
// Quality: 91/100 | Created: 2026-09-01

//# anyplot-orientation: square
const t = window.ANYPLOT_TOKENS;
const { width, height } = window.ANYPLOT_SIZE;

// --- Data: Scholar's Mate, final position after 4. Qxf7# --------------------
// Uppercase = white (K/Q/R/B/N/P), lowercase = black (k/q/r/b/n/p).
const pieces = {
  e1: "K",
  a1: "R",
  h1: "R",
  c1: "B",
  c4: "B",
  b1: "N",
  g1: "N",
  f7: "Q",
  a2: "P",
  b2: "P",
  c2: "P",
  d2: "P",
  e4: "P",
  f2: "P",
  g2: "P",
  h2: "P",
  e8: "k",
  d8: "q",
  a8: "r",
  h8: "r",
  c8: "b",
  f8: "b",
  c6: "n",
  f6: "n",
  a7: "p",
  b7: "p",
  c7: "p",
  d7: "p",
  e5: "p",
  g7: "p",
  h7: "p",
};

const GLYPHS = {
  K: "♔",
  Q: "♕",
  R: "♖",
  B: "♗",
  N: "♘",
  P: "♙",
  k: "♚",
  q: "♛",
  r: "♜",
  b: "♝",
  n: "♞",
  p: "♟",
};

// Piece identity (white/black) is fixed across themes, like the game itself —
// only the board and chrome follow ANYPLOT_THEME.
const PIECE_LIGHT = "#F2ECDA";
const PIECE_DARK = "#2A2822";

// --- Layout -------------------------------------------------------------
const margin = { top: 130, right: 60, bottom: 90, left: 90 };
const availW = width - margin.left - margin.right;
const availH = height - margin.top - margin.bottom;
const boardSize = Math.min(availW, availH);
const cell = boardSize / 8;
const boardX = margin.left + (availW - boardSize) / 2;
const boardY = margin.top + (availH - boardSize) / 2;

const files = ["a", "b", "c", "d", "e", "f", "g", "h"];

// --- SVG mount ------------------------------------------------------------
const svg = d3.select("#container").append("svg").attr("width", width).attr("height", height);

const board = svg.append("g").attr("transform", `translate(${boardX},${boardY})`);

// Column/row -> pixel placement via d3-scale bands (rank 8 at the top, rank 1
// at the bottom — white-at-bottom board orientation).
const xScale = d3.scaleBand().domain(d3.range(8)).range([0, boardSize]);
const yScale = d3.scaleBand().domain(d3.range(8)).range([boardSize, 0]);

// --- Squares (light square on h1, per standard chess convention) ----------
// Explicit per-theme board tones (not a pageBg/ink blend) so the light square
// is always the higher-luminance one in both themes. The previous
// `d3.interpolateRgb(t.pageBg, t.ink)` approach inverted in dark mode because
// `t.ink` flips from dark to light between themes, flipping the blend
// direction along with it.
const boardTones =
  t.theme === "dark" ? { light: "#403F38", dark: t.elevatedBg } : { light: t.elevatedBg, dark: "#C9C7C1" };
const squares = [];
for (let row = 0; row < 8; row++) {
  for (let col = 0; col < 8; col++) {
    squares.push({ col, row, light: (col + row) % 2 === 1 });
  }
}

board
  .selectAll("rect.square")
  .data(squares)
  .join("rect")
  .attr("class", "square")
  .attr("x", (d) => xScale(d.col))
  .attr("y", (d) => yScale(d.row))
  .attr("width", xScale.bandwidth())
  .attr("height", yScale.bandwidth())
  .attr("fill", (d) => (d.light ? boardTones.light : boardTones.dark));

board
  .append("rect")
  .attr("x", 0)
  .attr("y", 0)
  .attr("width", boardSize)
  .attr("height", boardSize)
  .attr("fill", "none")
  .attr("stroke", t.inkSoft)
  .attr("stroke-width", 2);

// --- Pieces (white glyphs get a light fill + dark stroke, black glyphs the
// reverse, so identity reads clearly on either square color or theme) ------
const pieceData = Object.entries(pieces).map(([square, code]) => {
  const col = files.indexOf(square[0]);
  const row = Number(square[1]) - 1;
  return { square, code, col, row, isWhite: code === code.toUpperCase() };
});

board
  .selectAll("text.piece")
  .data(pieceData)
  .join("text")
  .attr("class", "piece")
  .attr("x", (d) => xScale(d.col) + xScale.bandwidth() / 2)
  .attr("y", (d) => yScale(d.row) + yScale.bandwidth() / 2)
  .attr("text-anchor", "middle")
  .attr("dominant-baseline", "central")
  .style("font-family", "'DejaVu Sans', sans-serif")
  .style("font-size", `${cell * 0.72}px`)
  .style("paint-order", "stroke fill")
  .attr("fill", (d) => (d.isWhite ? PIECE_LIGHT : PIECE_DARK))
  .attr("stroke", (d) => (d.isWhite ? PIECE_DARK : PIECE_LIGHT))
  .attr("stroke-width", (d) => (d.isWhite ? cell * 0.02 : cell * 0.015))
  .text((d) => GLYPHS[d.code]);

// --- Coordinate labels (files below, ranks along the left) ----------------
board
  .selectAll("text.file-label")
  .data(files)
  .join("text")
  .attr("class", "file-label")
  .attr("x", (_, i) => xScale(i) + xScale.bandwidth() / 2)
  .attr("y", boardSize + 34)
  .attr("text-anchor", "middle")
  .attr("fill", t.inkSoft)
  .style("font-size", "20px")
  .text((f) => f);

board
  .selectAll("text.rank-label")
  .data(d3.range(1, 9))
  .join("text")
  .attr("class", "rank-label")
  .attr("x", -26)
  .attr("y", (r) => yScale(r - 1) + yScale.bandwidth() / 2)
  .attr("text-anchor", "middle")
  .attr("dominant-baseline", "central")
  .attr("fill", t.inkSoft)
  .style("font-size", "20px")
  .text((r) => r);

// --- Title ------------------------------------------------------------------
const title = "Scholar's Mate, Move 4 · chessboard-pieces · javascript · d3 · anyplot.ai";
const titleRatio = title.length > 67 ? 67 / title.length : 1.0;
const titleSize = Math.max(16, Math.round(22 * titleRatio));

svg
  .append("text")
  .attr("x", width / 2)
  .attr("y", 56)
  .attr("text-anchor", "middle")
  .attr("fill", t.ink)
  .style("font-size", `${titleSize}px`)
  .style("font-weight", "600")
  .text(title);

svg
  .append("text")
  .attr("x", width / 2)
  .attr("y", 88)
  .attr("text-anchor", "middle")
  .attr("fill", t.inkSoft)
  .style("font-size", "18px")
  .text("White delivers checkmate: 1.e4 e5 2.Bc4 Nc6 3.Qh5 Nf6?? 4.Qxf7#");

Retrieve this implementation

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

Part of Chess Board with Pieces for Position Diagrams on anyplot.ai.

Other implementations