I wanted to visualize an optimization for the Travelling Salesman problem because I'm trying to solve it. Ended up turning into this.
A Pen by HARUN PEHLİVAN on CodePen.
<div> | |
<canvas id="canvas" height="1600" width="1600"></canvas> | |
<canvas id="ref" height="1600" width="1600"></canvas> | |
</div> |
I wanted to visualize an optimization for the Travelling Salesman problem because I'm trying to solve it. Ended up turning into this.
A Pen by HARUN PEHLİVAN on CodePen.
const RAND = new Math.seedrandom('jake albaugh') | |
console.clear() | |
let PI = Math.PI | |
let cvs = document.querySelector('#canvas') | |
let ctx = cvs.getContext('2d') | |
let di = cvs.width | |
let POINTS = getPoints() | |
let ref_cvs = document.querySelector('#ref') | |
let ref_ctx = ref_cvs.getContext('2d') | |
let pt_count = POINTS.sorted.length | |
let direction = 'up' | |
drawPoints(POINTS) | |
tick(0) | |
function tick(index) { | |
ctx.clearRect(0, 0, di, di) | |
drawPoly(POINTS, index) | |
if(direction === 'up' && index === 5) { | |
direction = 'down' | |
} else if(direction === 'down' && index === 0) { | |
direction = 'up' | |
} | |
(direction === 'down') ? index-- : index++; | |
window.requestAnimationFrame(() => { | |
tick(index) | |
}) | |
} | |
function drawPoints(points) { | |
let dot = 4 | |
points.sorted.forEach((id, p) => { | |
let pt = points.points[id] | |
let hue = getHue(p, pt_count) | |
ref_ctx.fillStyle = `hsla(${hue}, 100%, 50%, 0.6)` | |
ref_ctx.beginPath() | |
ref_ctx.arc(pt.x, pt.y, dot, 0, 2 * PI) | |
ref_ctx.fill() | |
ref_ctx.closePath() | |
}) | |
} | |
function drawPoly(points, offset) { | |
points.sorted.forEach((id, p) => { | |
let pt = points.points[id] | |
let hue = getHue(p, pt_count) | |
ctx.fillStyle = `hsla(${hue}, 100%, 50%, 0.1)` | |
ctx.strokeStyle = `hsla(${hue}, 100%, 50%, 0.3)` | |
ctx.lineWidth = 2 | |
ctx.beginPath(); | |
ctx.moveTo(pt.x, pt.y); | |
for(let i = 0; i < 3; i++) { | |
let id = pt.prox[i + offset]._id | |
let prox = points.points[id] | |
points.points[id].usage++ | |
ctx.lineTo(prox.x, prox.y) | |
} | |
ctx.lineTo(pt.x, pt.y) | |
ctx.fill() | |
ctx.stroke() | |
ctx.closePath() | |
}) | |
} | |
function getHue(p, count) { | |
return 360 * (p / count) | |
} | |
function getPoints() { | |
let id = 0 | |
let points = [] | |
for(let i = 0; i < 1000; i++) points.push(plot(random(), random())); | |
points.forEach(point => { | |
point.prox = [] | |
points.forEach(sib => { | |
if(sib._id !== point._id) { | |
point.prox.push({ | |
_id: sib._id, | |
dist: distance(point.rel_x, point.rel_y, sib.rel_x, sib.rel_y) | |
}) | |
} | |
}) | |
point.prox.sort((a, b) => { | |
if (a.dist > b.dist) return 1; | |
if (a.dist < b.dist) return -1; | |
return 0; | |
}) | |
}) | |
let sorted = Array.from(points).sort((a, b) => { | |
if(a.dist_c > b.dist_c) return 1; | |
if(a.dist_c < b.dist_c) return -1; | |
return 0; | |
}).map(i => { return i._id }) | |
return { points: points, sorted: sorted } | |
function plot(x, y) { | |
let scale_x = x * di | |
let scale_y = y * di | |
let rel_x = di/-2 + scale_x | |
let rel_y = di/-2 + scale_y | |
let dist_c = distance(rel_x, rel_y) | |
return { | |
_id: id++, | |
x: scale_x, rel_x: rel_x, | |
y: scale_y, rel_y: rel_y, | |
dist_c: dist_c, | |
usage: 0 | |
} | |
} | |
} | |
function random() { | |
return RAND.quick() | |
} | |
function distance(x1, y1, x2 = 0, y2 = 0) { | |
let x = x1 - x2 | |
let y = y1 - y2 | |
return Math.sqrt(x * x + y * y) | |
} |
<script src="https://cdnjs.cloudflare.com/ajax/libs/seedrandom/2.4.0/seedrandom.min.js"></script> |
body { | |
background: #060606; | |
} | |
$cvs-di: 800px; | |
canvas { | |
height: auto; | |
width: $cvs-di; | |
position: absolute; | |
top: 50%; left: 50%; | |
transform: translate(-50%, -50%); | |
} |