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.