aboutsummaryrefslogtreecommitdiffstats
path: root/main.js
diff options
context:
space:
mode:
authors-ol <s+removethis@s-ol.nu>2023-05-05 19:54:08 +0000
committers-ol <s+removethis@s-ol.nu>2023-05-05 19:54:08 +0000
commite447f2d9ca1de1a33d0d25c0a8395d72a4035553 (patch)
tree3cf0650cdefcd08fabe7992ad8d02a6b3b40d2bd /main.js
parentupgrade dependencies (diff)
downloadisomorphic-kb-explorer-e447f2d9ca1de1a33d0d25c0a8395d72a4035553.tar.gz
isomorphic-kb-explorer-e447f2d9ca1de1a33d0d25c0a8395d72a4035553.zip
restart with v2
Diffstat (limited to 'main.js')
-rw-r--r--main.js276
1 files changed, 276 insertions, 0 deletions
diff --git a/main.js b/main.js
new file mode 100644
index 0000000..85c34b9
--- /dev/null
+++ b/main.js
@@ -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();