diff --git a/index.css b/index.css new file mode 100644 index 0000000..3b58bef --- /dev/null +++ b/index.css @@ -0,0 +1,46 @@ +* { + color: #eee; + font-family: 'Courier New', Courier, monospace; + font-size: 10svh; + margin: 0; + user-select: none; + box-sizing: border-box; + overflow: hidden; +} + +body { + height: 100svh; + background-color: #333; +} + +input{ + display: none; +} + +label { + position: fixed; + bottom: 0; + margin: 2svh; + display: flex; + justify-content: center; + align-items: center; + background-color: #444; + border: 2px solid #222; + border-radius: 3svh; + width: 10svh; + height: 10svh; + cursor: pointer; +} + +input:checked+label { + background-color: #AAA; + border-color: #666; +} + +label[for="add-point"] { + left: 0; +} + +label[for="remove-point"] { + right: 0; +} diff --git a/index.html b/index.html new file mode 100644 index 0000000..a13d8c6 --- /dev/null +++ b/index.html @@ -0,0 +1,18 @@ + + + + + + + closest-pairs + + + + + + + + + + + diff --git a/index.js b/index.js new file mode 100644 index 0000000..8100eb3 --- /dev/null +++ b/index.js @@ -0,0 +1,156 @@ +let canvas; +let ctxt; + +let addingPoint; + +let points; + +window.onload = () => { + canvas = document.getElementById("canvas"); + canvas.width = window.innerWidth; + canvas.height = window.innerHeight; + ctxt = canvas.getContext("2d"); + + addingPoint = document.getElementById("add-point"); + + points = []; + + canvas.onclick = e => { + let x = e.clientX; + let y = e.clientY; + + if(addingPoint.checked) { + // insert sorted by x then y + let inserted = false; + for(let i=0;i { + canvas.width = window.innerWidth; + canvas.height = window.innerHeight; + + update(); +} + +function update() { + ctxt.clearRect(0, 0, canvas.width, canvas.height); + + if(points.length < 2) { + renderPoints(points); + return + } + + let [index1, index2] = calculateClosestPair(points) + + renderClosest(points, index1, index2); + renderPoints(points); +} + +function renderClosest(points, index1, index2) { + ctxt.lineWidth = canvas.height / 300; + ctxt.strokeStyle = "#FFF"; + ctxt.lineCap = "round"; + ctxt.beginPath(); + ctxt.moveTo(points[index1][0], points[index1][1]); + ctxt.lineTo(points[index2][0], points[index2][1]); + ctxt.stroke(); + + ctxt.beginPath(); + ctxt.arc(points[index1][0], points[index1][1], canvas.height / 100, 0, 2 * Math.PI); + ctxt.stroke(); + + ctxt.beginPath(); + ctxt.arc(points[index2][0], points[index2][1], canvas.height / 100, 0, 2 * Math.PI); + ctxt.stroke(); +} + +function renderPoints(points) { + ctxt.fillStyle = "#000A"; + for(let point of points) { + ctxt.beginPath(); + ctxt.arc(point[0], point[1], canvas.height / 100, 0, 2 * Math.PI); + ctxt.fill(); + } +} + +function calculateClosestPair(points) { + let minDistance = Infinity; + let bestIndex1 = 0; + let bestIndex2 = 0; + let activeIndexStart = 0; + + for(let i=0;i= minDistance) { + activeIndexStart++; + } else { + // other points are still active + break; + } + } + + for(let j=activeIndexStart;j minDistance) { + // we don't need expensive hypot calculation + continue + } + if(signedYDistance < -minDistance) { + // there cannot be better points + break + } + + // it's close so we check using hypot + let distance = Math.hypot(signedYDistance, points[j][0] - points[i][0]); + if(distance < minDistance) { + minDistance = distance; + bestIndex1 = j; + bestIndex2 = i; + } + } + } + + return [bestIndex1, bestIndex2]; +}