Ulam Spiral
by Chris Ball
HTML
<svg
version="1.2"
width="500"
height="500"
viewBox="0 0 500 500"
xmlns="http://www.w3.org/2000/svg"
preserveAspectRatio="xMidYMid meet"
id="canvas"></svg>
CSS
#canvas {
/* background-image: url(https://empslocal.ex.ac.uk/people/staff/mrwatkin/zeta/ulam.gif); */
background-repeat: no-repeat;
background-position: center;
background-size: contain;
height: auto;
width: 100%;
}
JavaScript
function numbersUntil(limit, start = 1) {
return Array.from({length: limit}, (_, i) => i + start)
}
function isSquare(n) {
return n > 0 && Math.sqrt(n) % 1 === 0
}
const isPrime = num => {
for (let i = 2, s = Math.sqrt(num); i <= s; i++) {
if (num % i === 0) return false
}
return num > 1
}
function appendSVGChild(elementType, target, attributes = {}, text = '') {
const element = document.createElementNS('http://www.w3.org/2000/svg', elementType)
Object.entries(attributes).map(a => element.setAttribute(a[0],a[1]))
if (text) {
const textNode = document.createTextNode(text)
element.appendChild(textNode)
}
target.appendChild(element)
return element
}
const canvas = document.getElementById('canvas')
const canvasSize = canvas.getBoundingClientRect()
const canvasCentre = {
x: canvasSize.width / 2,
y: canvasSize.height / 2
}
const size = 30
function radius(num) {
return isPrime(num) ? 0.5 : 0
}
class UlamSpiral {
#points = []
#currentValue = 1
constructor(maxSideLength) {
this.xpos = Math.ceil( maxSideLength / 2 )
this.ypos = Math.ceil( maxSideLength / 2 )
for (let currentSquareSize = 1; currentSquareSize <= maxSideLength; currentSquareSize++) {
// For squares composed of even-numbered sides, the starting position is bottom right.
if (currentSquareSize % 2 === 0) {
this.xpos++
this.drawRight(currentSquareSize)
this.drawTop(currentSquareSize)
}
// For squares composed of odd-numbered sides, the starting position is top left.
else {
this.xpos--;
this.drawLeft(currentSquareSize)
this.drawBottom(currentSquareSize)
}
}
return this.#points
}
drawBottom(currentSquareSize) {
for (let currSideLength = 1; currSideLength < currentSquareSize; currSideLength++) {
this.xpos++
this.setPoint(this.#currentValue)
this.#currentValue++
}
}
drawRight(currentSquareSize) {
for (let currSideLength = 1; currSideLength <= currentSquareSize; currSideLength++)...