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%); | |
| } |