Another visualization of Travelling Salesman Sketches: Point-out Proximal Checker
A Pen by HARUN PEHLİVAN on CodePen.
<div> | |
<select id="scale"></select> | |
<select id="start"></select> | |
</div> | |
<canvas height="800" width="800"></canvas> |
console.clear(); | |
const PI = Math.PI; | |
const PI2 = PI * 2; | |
class Canvas { | |
constructor() { | |
this.element = document.querySelector('canvas'); | |
this.context = this.element.getContext('2d'); | |
this.diameter = this.element.width; | |
this.gutter = this.diameter * 0.05;; | |
} | |
clear() { | |
this.context.clearRect(0, 0, this.diameter, this.diameter); | |
} | |
point(x, y, color = 'red', radius = 4) { | |
this.context.fillStyle = color; | |
this.context.beginPath(); | |
this.context.arc(this.relative(x), this.relative(y), radius, 0, PI2); | |
this.context.fill(); | |
} | |
diamond(x, y, scale, color = 'red') { | |
this.context.fillStyle = color; | |
this.context.beginPath(); | |
let relScale = scale / 2; | |
let x1 = this.relative(x); | |
let y1 = this.relative(y - relScale); | |
let x2 = this.relative(x + relScale); | |
let y2 = this.relative(y); | |
let x3 = this.relative(x); | |
let y3 = this.relative(y + relScale); | |
let x4 = this.relative(x - relScale); | |
let y4 = this.relative(y); | |
this.context.moveTo(x1, y1); | |
this.context.lineTo(x2, y2); | |
this.context.lineTo(x3, y3); | |
this.context.lineTo(x4, y4); | |
this.context.fill(); | |
} | |
line(from, to, color = 'red', width = 1) { | |
this.context.beginPath(); | |
this.context.strokeStyle = color; | |
this.context.lineWidth = width; | |
this.context.moveTo(this.relative(from[0]), this.relative(from[1])); | |
this.context.lineTo(this.relative(to[0]), this.relative(to[1])); | |
this.context.stroke(); | |
} | |
relative(pos) { | |
return pos * (this.diameter - (this.gutter * 2)) + this.gutter; | |
} | |
} | |
class Solver { | |
constructor({ scale, start }) { | |
this.canvas = new Canvas(); | |
this.update({ scale, start }); | |
this.draw(); | |
} | |
update({ scale = 0.125, start = 'C' }) { | |
this.scale = scale; | |
this.radius = Math.round(scale * 6) + 1; | |
this.speed = Math.round(scale * 40); | |
let startPlots = { | |
C: [0.5, 0.5], | |
NE: [1 - scale, scale], | |
SE: [1 - scale, 1 - scale], | |
SW: [scale, 1 - scale], | |
NW: [scale, scale], | |
}[start]; | |
this.startX = startPlots[0]; | |
this.startY = startPlots[1]; | |
this.NW = this.startX <= 0.5 && this.startY <= 0.5; | |
this.NE = this.startX > 0.5 && this.startY <= 0.5; | |
this.SE = this.startX > 0.5 && this.startY > 0.5; | |
this.SW = this.startX <= 0.5 && this.startY > 0.5; | |
this.generate(); | |
} | |
generate() { | |
this.points = []; | |
let steps = 1 / this.scale; | |
let rad = this.scale / 2; | |
let points = []; | |
for (let y = 0; y <= steps; y++) { | |
let relY = y * this.scale; | |
for (let x = 0; x <= steps; x++) { | |
let relX = x * this.scale; | |
this.addPoint(relX, relY); | |
if (x < steps && y < steps) { | |
this.addPoint(relX + rad, relY + rad); | |
} | |
} | |
} | |
this.pointCount = this.points.length; | |
this.sortPoints(); | |
this.progress = 0; | |
this.pointIdx = 0; | |
} | |
sortPoints() { | |
this.points = this.points.sort((a, b) => { | |
let ax = a[0], bx = b[0]; | |
let ay = a[1], by = b[1]; | |
let aprox = a[2], bprox = b[2]; | |
// perfer proximity to center | |
if (aprox.C > bprox.C) return 1; | |
if (aprox.C < bprox.C) return -1; | |
// prefer radial proximity | |
if (aprox.R > bprox.R) return 1; | |
if (aprox.R < bprox.R) return -1; | |
return 0; | |
}); | |
} | |
addPoint(x, y) { | |
let prox = { | |
C: Math.hypot(this.startX - x, this.startY - y), | |
R: Math.atan((0.5 - y) / (0.5 - x)) * (180 / PI) | |
} | |
this.points.push([x, y, prox]); | |
} | |
draw() { | |
if (this.progress % this.speed === 0) { | |
this.canvas.clear(); | |
this.points.forEach((point) => { | |
this.canvas.point(point[0], point[1], '#222', this.radius); | |
}); | |
let point1 = this.points[this.pointIdx % this.pointCount]; | |
let x1 = point1[0], | |
y1 = point1[1]; | |
this.canvas.diamond(x1, y1, this.scale, 'red'); | |
let fuzz1 = 2; | |
let fuzz2 = 3; | |
// future points | |
for (let i = 1; i < 1 + fuzz1; i++) { | |
let id = (this.pointIdx % this.pointCount) + i; | |
if (id < this.pointCount) { | |
let point = this.points[id]; | |
this.canvas.point(point[0], point[1], 'white', this.radius); | |
} | |
} | |
// previous points | |
for (let i = 0; i < (this.pointIdx % this.pointCount); i++) { | |
let point = this.points[i]; | |
let x = point[0], | |
y = point[1]; | |
if ((this.pointIdx % this.pointCount) - i < fuzz2) { | |
this.canvas.diamond(x, y, this.scale, 'red'); | |
} else { | |
this.canvas.diamond(x, y, this.scale, 'rgba(100, 100, 100, 0.2)'); | |
} | |
} | |
this.pointIdx++; | |
} | |
this.progress++; | |
window.requestAnimationFrame(this.draw.bind(this)); | |
} | |
} | |
// let scales = [0.03125, 0.0625, 0.125, 0.25, 0.5, 1]; | |
let scales = [0.0625, 0.125, 0.25, 0.5, 1]; | |
let starts = ['C', 'NW', 'NE', 'SE', 'SW']; | |
let currScale = 1; | |
let currStart = 0; | |
let $scale = document.querySelector('#scale'); | |
let $start = document.querySelector('#start'); | |
buildScale(); | |
buildStart(); | |
$scale.addEventListener('change', update); | |
$start.addEventListener('change', update); | |
let solver = new Solver({ scale: scales[currScale], start: starts[currStart] }); | |
function update() { | |
let scale = parseFloat($scale.value); | |
let start = $start.value; | |
solver.update({ scale, start }); | |
} | |
function buildScale() { | |
for (let i = 0; i < scales.length; i++) { | |
let $opt = document.createElement('option'); | |
$opt.value = scales[i]; | |
$opt.innerHTML = scales[i]; | |
if (i === currScale) $opt.setAttribute('selected', true); | |
$scale.appendChild($opt); | |
} | |
} | |
function buildStart() { | |
for (let i = 0; i < starts.length; i++) { | |
let $opt = document.createElement('option'); | |
$opt.value = starts[i]; | |
$opt.innerHTML = starts[i]; | |
if (i === currStart) $opt.setAttribute('selected', true); | |
$start.appendChild($opt); | |
} | |
} |
body { | |
background: #121212; | |
} | |
div { | |
margin: 1rem 0 0; | |
text-align: center; | |
} | |
canvas { | |
height: auto; | |
width: 95%; | |
max-width: 600px; | |
margin: 1rem auto; | |
display: block; | |
background: black; | |
} |
Another visualization of Travelling Salesman Sketches: Point-out Proximal Checker
A Pen by HARUN PEHLİVAN on CodePen.