diff options
| author | s-ol <s+removethis@s-ol.nu> | 2023-05-05 19:54:08 +0000 |
|---|---|---|
| committer | s-ol <s+removethis@s-ol.nu> | 2023-05-05 19:54:08 +0000 |
| commit | e447f2d9ca1de1a33d0d25c0a8395d72a4035553 (patch) | |
| tree | 3cf0650cdefcd08fabe7992ad8d02a6b3b40d2bd /main.js | |
| parent | upgrade dependencies (diff) | |
| download | isomorphic-kb-explorer-e447f2d9ca1de1a33d0d25c0a8395d72a4035553.tar.gz isomorphic-kb-explorer-e447f2d9ca1de1a33d0d25c0a8395d72a4035553.zip | |
restart with v2
Diffstat (limited to 'main.js')
| -rw-r--r-- | main.js | 276 |
1 files changed, 276 insertions, 0 deletions
@@ -0,0 +1,276 @@ +const svg = document.getElementById('diagram'); +const arrows = Array.from(svg.querySelectorAll('.arrow')); +const preset = document.getElementById('layout-preset'); +const controls = Array.from(document.querySelectorAll('.axis-control')); +const steps = Array.from(document.querySelectorAll('.axis-control > input')); +const dirs = Array.from(document.querySelectorAll('.axis-control .dir input')); + +let state = [ 7, 2, null ]; +let last = 0; +let selected = null; + +const completeState = ([a, b, c]) => { + if (a == null) { + a = b - c; + } else if (b == null) { + b = c + a; + } else { + c = b - a; + } + return [a, b, c, -a, -b, -c]; +}; + +const updateValues = () => { + const full = completeState(state); + + full.forEach((val, i) => { + arrows[i].querySelector('text').textContent = val; + + if (i < steps.length) steps[i].value = Math.abs(val); + if (i < dirs.length) { + dirs[i].checked = val < 0; + dirs[i].disabled = val == 0; + } + }); + updateFocus(); +}; + +const updateFocus = () => { + const full = completeState(state); + + controls.forEach((control, i) => { + let className = 'axis-control'; + if (state[i] == null) className += ' driven'; + else if (i == selected) className += ' selected'; + if (state[i] == 0) className += ' dir-disabled'; + control.className = className; + }); + + full.forEach((val, i) => { + const positive = val > 0 || (val == 0 && i < 3); + + let className = 'arrow'; + if (state[i%3] == null) className += ' driven'; + if (!positive) className += ' negative'; + if (i%3 == selected) className += ' selected'; + arrows[i].className.baseVal = className; + }); +}; + +const select = (i) => { + if (state[i] != null) return false; + + const newState = [null, null, null]; + newState[i] = completeState(state)[i]; + newState[last] = state[last]; + state = newState; + last = i; + + return true; +}; + +arrows.forEach((arrow, i) => { + arrow.onclick = () => steps[i%3].focus(); +}); + +controls.forEach((control, i) => { + control.addEventListener('focusin', () => { selected = i; select(i); updateFocus(); }); + control.addEventListener('focusout', () => { selected = null; updateFocus(); }); +}); + +steps.forEach((input, i) => { + input.onchange = () => { + select(i); + state[i] = +input.value; + if (dirs[i].checked) state[i] = -state[i]; + preset.value = 'custom'; + updateValues(); + }; +}); + +dirs.forEach((input, i) => { + input.onchange = () => { + select(i); + state[i] = state[i] * -1; + preset.value = 'custom'; + updateValues(); + }; +}); + +let rot = 0; +let targetRot = 0; + +document.getElementById('turn-ccw').onclick = () => { + targetRot -= 30; +}; +document.getElementById('turn-cw').onclick = () => { + targetRot += 30; +}; + +preset.onchange = () => { + switch (preset.value) { + case 'custom': return; + case 'wicki-hayden': state = [ 7, 2, null ]; break; + case 'janko': state = [ 1, 2, null ]; break; + case 'harmonic': state = [ 7, 4, null ]; break; + case 'gerhard': state = [ 4, 3, null ]; break; + } + + last = state.findIndex(s => s != null); + updateValues(); +}; +preset.value = 'wicki-hayden'; +preset.onchange(); + +const bg = document.getElementById('canvas-bg').getContext('2d'); +const fg = document.getElementById('canvas-fg').getContext('2d'); + +const sqrt3 = Math.sqrt(3); +const sqrt32 = sqrt3 / 2; + +const size = 80; +const hs = size / 2; +const hh = sqrt32 * size; + +const hex2px = (q, r) => { + const x = size * (3/2 * r); + const y = -size * (sqrt32 * r + sqrt3 * q); + return [x, y]; +}; + +const mul = (x, y, mat) => { + return [ + x * mat[0] + y * mat[1] + mat[2], + y * mat[3] + y * mat[4] + mat[5], + ]; +}; + +const updateRotation = () => { + const delta = targetRot - rot; + if (Math.abs(delta) < 1) rot = targetRot; + else rot += delta * 0.1; + + if (rot < 60 && rot > -60) return; + + const full = completeState(state); + let driven = state.indexOf(null); + + // cw rotation + while (rot >= 60) { + rot -= 60; + targetRot -= 60; + full.unshift(full.pop()); + driven++; + } + + // cw rotation + while (rot <= -60) { + rot += 60; + targetRot += 60; + full.push(full.shift()); + driven = (driven-1+3) % 3; + } + + state = full.slice(0, 3); + state[driven%3] = null; + + updateValues(); +}; + +const updateBackground = (canvasSize) => { + bg.strokeStyle = '#b9bdc1'; + bg.strokeWidth = 1.5; + + bg.canvas.width = bg.canvas.width; + bg.translate(canvasSize/2, canvasSize/2); + + const rad = Math.ceil(canvasSize / size / 3); + for (let q = -rad; q <= rad; q++) { + const rMin = Math.max(-rad, -q-rad); + const rMax = Math.min(rad, rad-q); + for (let r = rMin; r <= rMax; r++) { + bg.save(); + bg.translate(...hex2px(q, r)); + + bg.beginPath(); + bg.moveTo(-hs, hh); + bg.lineTo(hs, hh); + bg.lineTo(size, 0); + bg.lineTo(hs, -hh); + bg.lineTo(-hs, -hh); + bg.lineTo(-size, 0); + bg.closePath(); + bg.stroke(); + + bg.restore(); + } + } +}; + +const main = document.querySelector('main'); + +let mousePos = [0, 0]; +main.onmousemove = (e) => { + mousePos = [ + e.clientX - main.clientWidth/2, + e.clientY - main.clientHeight/2, + ]; +}; + +let lastCanvasSize = 0; +const draw = () => { + const width = window.innerWidth; + const height = window.innerHeight; + + const canvasSize = Math.sqrt(width*width + height*height); + + if (canvasSize !== lastCanvasSize) { + bg.canvas.width = bg.canvas.height = canvasSize; + fg.canvas.width = fg.canvas.height = canvasSize; + lastCanvasSize = canvasSize; + + updateBackground(canvasSize); + } + + fg.canvas.width = fg.canvas.width; + + updateRotation(); + + fg.translate(canvasSize/2, canvasSize/2); + + fg.fillStyle = '#303336'; + fg.font = `${size*0.8}px sans-serif`; + fg.textAlign = 'center'; + + const rotRad = rot / 180 * Math.PI; + + const [qq, rr] = completeState(state); + const rad = Math.ceil(canvasSize / size / 3); + for (let q = -rad; q <= rad; q++) { + const rMin = Math.max(-rad, -q-rad); + const rMax = Math.min(rad, rad-q); + for (let r = rMin; r <= rMax; r++) { + fg.save(); + fg.translate(...hex2px(q, r)); + + const val = q*qq + r*rr; + fg.rotate(-rotRad); + fg.fillText(val, 0, size/3); + + fg.restore(); + } + } + + const mouse = [ + Math.cos(rotRad) * mousePos[0] + Math.sin(rotRad) * mousePos[1], + -Math.sin(rotRad) * mousePos[0] + Math.cos(rotRad) * mousePos[1], + ]; + + fg.arc(mouse[0], mouse[1], 5, 0, 2*Math.PI); + fg.fill() + + document.body.style.setProperty('--global-rot', `${rot}deg`); + requestAnimationFrame(draw); +}; + +draw(); |
