Published
Edited
Sep 20, 2022
1 fork
Importers
21 stars
Insert cell
Insert cell
Insert cell
chessboard()
Insert cell
Insert cell
Insert cell
chessboard('1K1k4/1P6/8/8/8/8/r7/2R5')
Insert cell
Insert cell
chessboard('start', { size: 150 })
Insert cell
Insert cell
chessboard('start', { theme: {
lightSquareFill: '#FFE',
darkSquareFill: '#263',
borderStroke: '#263',
labelColor: '#263',
}})
Insert cell
Insert cell
chessboard('start', { call: board => board.querySelector('.square-d1').setAttribute('fill', '#D00') })
Insert cell
Insert cell
chessboard('8/8/2r5/8/8/8/8/8', {
call: (board, state) => {
d3.select(board)
.append('g')
.selectAll('g')
.data([
['a6', 'b6'],
['c8', 'c7'],
['c1', 'c5'],
['h6', 'd6'],
])
.join('line')
.attr('x1', d => state.squareCentroid(d[0])[0])
.attr('y1', d => state.squareCentroid(d[0])[1])
.attr('x2', d => state.squareCentroid(d[1])[0])
.attr('y2', d => state.squareCentroid(d[1])[1])
.attr('stroke', 'hsla(200, 80%, 30%, 50%)')
.attr('stroke-width', '6')
.attr('stroke-linecap', 'round')
}
})
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
querySquare('rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1', 'd1')
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
positionModifier = fn => {
return (fenString, ...args) => {
const [position, ...fields] = fenString.split(' ')
const newPosition = fn(position, ...args)
return [newPosition, ...fields].join(' ')
}
}
Insert cell
Insert cell
squareToCoords = (square) => {
const [file, rank] = Array.from(square)
return [RANKS.indexOf(rank), FILES.indexOf(file)]
}
Insert cell
coordsToSquare = ([row, col]) => FILES[col] + RANKS[row]
Insert cell
Insert cell
// TODO: Add validation
rowToArray = (rowString) => Array.from(rowString).reduce((acc, char) => {
// If character is a number, add that many blank squares
if (RegExp(/[1-8]/).test(char)) return acc.concat(new Array(+char).fill(null))
// Otherwise, assume character is a piece marker and add it on its own
return acc.concat([char])
}, [])
Insert cell
// TODO: Add validation
fenToArray = (fenString) => fenString.split(' ')[0].split('/').map(rowToArray)
Insert cell
arrayToRow = (arr) => {
if (arr.length !== FILES.length) {
throw `Row array must have exactly ${FILES.length} entries, representing eight squares in a chessboard row`
}
let counter = 0
let emptySquareCounter = 0
let rowString = ''
while (counter < 8) {
// If the character is a chess piece, first add any built up empty square
// as a number, then add the piece to the string, then reset the empty square counter,
// then increment the counter
if (Array.from(PIECE_MAP.keys()).includes(arr[counter])) {
if (emptySquareCounter !== 0) rowString += emptySquareCounter
rowString += arr[counter]
emptySquareCounter = 0
counter++
// If the character is null, incement the empty square counter, then increment the counter
} else if (arr[counter] === null) {
emptySquareCounter++
counter++
// If the character is neither null nor a valid piece, throw an error
} else {
throw `${arr[counter]} is an invalid value for a chessboard square`
}
}
// Finally, flush the remaining empty squares if non-zero
if (emptySquareCounter !== 0) rowString += emptySquareCounter
return rowString
}
Insert cell
arrayToPosition = (arr) => {
if (arr.length !== RANKS.length) {
throw `Board array must have exactly ${RANKS.length} entries, representing eight rows in a chessboard`
}

return arr.map(arrayToRow).join('/')
}
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
START_FEN = 'rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1'
Insert cell
Insert cell
chessboard = (fenString = 'start', opts) => {
const {
size: maxSize,
responsive,
call,
theme,
} = Object.assign({
size: 440,
responsive: true,
call: () => {},
theme: {
lightSquareFill: '#FFF',
darkSquareFill: '#EEE',
borderStroke: '#CCC',
labelColor: '#CCC',
}
}, opts)
const size = responsive ? Math.min(maxSize, width) : maxSize
const marginSize = 20
const innerSize = size - marginSize * 2
const squareSize = innerSize / 8

const coords = fenToArray(fenString === 'start' ? START_FEN : fenString)

const output = html`
<svg class="chessboard" viewBox="0 0 ${size} ${size}" width=${size} height=${size}>
<defs>
${pieceSVG}
</defs>
<g class="labels file-labels">
${FILES.map((label, i) => svg`
<text
class="label file-label file-label-top"
x=${marginSize + i * squareSize + squareSize / 2}
y="12"
text-anchor="middle"
font-family="var(--sans-serif)"
font-size="14"
fill=${theme.labelColor}
>${label}</text>
<text
class="label file-label file-label-bottom"
x=${marginSize + i * squareSize + squareSize / 2}
y=${marginSize + innerSize + 16}
text-anchor="middle"
font-family="var(--sans-serif)"
font-size="14"
fill=${theme.labelColor}
>${label}</text>
`)}
</g>
<g class="labels rank-labels">
${RANKS.map((label, i) => svg`
labelText(label, 0, marginSize + i * squareSize + squareSize / 2 + 7, theme)
<text
class="label rank-label rank-label-left"
x="0"
y=${marginSize + i * squareSize + squareSize / 2 + 7}
text-anchor="right"
font-family="var(--sans-serif)"
font-size="14"
fill=${theme.labelColor}
>${label}</text>
<text
class="label rank-label rank-label-right"
x=${marginSize + innerSize + 12}
y=${marginSize + i * squareSize + squareSize / 2 + 7}
text-anchor="left"
font-family="var(--sans-serif)"
font-size="14"
fill=${theme.labelColor}
>${label}</text>
`)}
</g>
<g class="squares" transform="translate(${marginSize} ${marginSize})">
${coords.map(
(row, i) => svg`${row.map(
(square, j) => svg`
<rect
class="square ${(i + j) % 2 === 0 ? 'light-square' : 'dark-square'} square-${coordsToSquare([i, j])}"
x=${j * squareSize}
y=${i * squareSize}
width=${squareSize}
height=${squareSize}
fill=${(i + j) % 2 === 0 ? theme.lightSquareFill : theme.darkSquareFill}
/>
`
)}`
)}
</g>
<g class="pieces" transform="translate(${marginSize} ${marginSize})">
${coords.map(
(row, i) => svg`${
row.map(
(square, j) => square ? svg`<use
class="piece piece-${PIECE_MAP.get(square)}"
href="${new URL(`#${PIECE_MAP.get(square)}`, location)}"
x=${j * squareSize}
y=${i * squareSize}
width=${squareSize}
height=${squareSize}
/>` : null
).filter(d => d)
}`
)}
</g>
<rect
class="board-outline"
x=${marginSize}
y=${marginSize}
width=${innerSize}
height=${innerSize}
stroke=${theme.borderStroke}
fill="none"
/>
</svg>
`
// Call the passed in modifier function with helpful utilities
call(output, {
fenString,
squareSize,
squareCoords: (square) => squareToCoords(square).map(n => n * squareSize + marginSize),
squareCentroid: (square) => {
return squareToCoords(square).map(n => n * squareSize + 0.5 * squareSize + marginSize)
},
})
return output
}
Insert cell
Insert cell
Insert cell
pieceSVG = FileAttachment("chesspieces.svg").text()
Insert cell
Insert cell
d3 = require('d3')
Insert cell

One platform to build and deploy the best data apps

Experiment and prototype by building visualizations in live JavaScript notebooks. Collaborate with your team and decide which concepts to build out.
Use Observable Framework to build data apps locally. Use data loaders to build in any language or library, including Python, SQL, and R.
Seamlessly deploy to Observable. Test before you ship, use automatic deploy-on-commit, and ensure your projects are always up-to-date.
Learn more