From 5ebb87db41216d8df00ff535395593a4fdecd1e7 Mon Sep 17 00:00:00 2001 From: Otto Date: Fri, 5 Jun 2026 09:25:13 +0200 Subject: [PATCH] Make second brain graph viewport circular --- static/style.css | 2 +- templates/dashboard.html | 63 ++++++++++++++++++++++++++-------------- 2 files changed, 43 insertions(+), 22 deletions(-) diff --git a/static/style.css b/static/style.css index 34a2356..efdb15a 100644 --- a/static/style.css +++ b/static/style.css @@ -168,7 +168,7 @@ body { margin: 8px auto 0; background:#02040a; border:1px solid #172033; - border-radius: 8px; + border-radius: 50%; box-shadow: 0 0 28px rgba(34,211,238,0.08), inset 0 0 42px rgba(124,58,237,0.08); touch-action: none; } diff --git a/templates/dashboard.html b/templates/dashboard.html index 853102f..3d631f9 100644 --- a/templates/dashboard.html +++ b/templates/dashboard.html @@ -435,9 +435,11 @@ function _graphCtx() { return _graphCanvas().getContext('2d'); } function _graphResizeCanvas() { const canvas = _graphCanvas(); if (!canvas) return; - const w = canvas.parentElement.clientWidth - 24; - canvas.width = Math.max(320, w); - canvas.height = Math.max(560, Math.min(980, (window.innerHeight || 900) - 250)); + const availableW = Math.max(320, canvas.parentElement.clientWidth - 24); + const availableH = Math.max(360, (window.innerHeight || 900) - 250); + const size = Math.max(320, Math.min(availableW, availableH, 920)); + canvas.width = size; + canvas.height = size; } function _graphResetData() { @@ -494,10 +496,10 @@ function _graphSourceCenter(source) { } const i = graphState.sourceIndex.get(key); const angle = i * 2.399963; - const ring = i < 1 ? 0 : 260 + Math.sqrt(i) * 95; + const ring = i < 1 ? 0 : 210 + Math.sqrt(i) * 80; return { x: Math.cos(angle) * ring, - y: Math.sin(angle) * ring * 0.82, + y: Math.sin(angle) * ring, }; } @@ -507,12 +509,12 @@ function _graphPlaceNode(n) { } if (n.kind === 'tag') { const angle = _graphHashUnit(n.id) * Math.PI * 2; - const ring = 520 + (_graphHashUnit(n.id + ':r') * 420); + const ring = 470 + (_graphHashUnit(n.id + ':r') * 260); return {x: Math.cos(angle) * ring, y: Math.sin(angle) * ring}; } if (n.kind === 'host') { const angle = _graphHashUnit(n.id) * Math.PI * 2; - const ring = 760 + (_graphHashUnit(n.id + ':r') * 260); + const ring = 640 + (_graphHashUnit(n.id + ':r') * 220); return {x: Math.cos(angle) * ring, y: Math.sin(angle) * ring}; } @@ -523,11 +525,11 @@ function _graphPlaceNode(n) { graphState.sourceCounts.set(source, local); const center = _graphSourceCenter(source); const angle = local * 2.399963 + _graphHashUnit(n.id) * 0.7; - const radius = 18 + Math.sqrt(local) * 9 + _graphHashUnit(n.id + ':r') * 38; - const squash = 0.72 + _graphHashUnit(source) * 0.46; + const radius = 18 + Math.sqrt(local) * 7.5 + _graphHashUnit(n.id + ':r') * 28; + const squash = 0.94 + (_graphHashUnit(source) - 0.5) * 0.16; return { x: center.x + Math.cos(angle) * radius * squash, - y: center.y + Math.sin(angle) * radius * (1.55 - squash), + y: center.y + Math.sin(angle) * radius / squash, }; } @@ -1141,7 +1143,14 @@ function _graphDraw() { const hint = document.getElementById('graphHint'); ctx.clearRect(0,0,canvas.width,canvas.height); - const bg = ctx.createRadialGradient(canvas.width * 0.52, canvas.height * 0.48, 10, canvas.width * 0.5, canvas.height * 0.5, Math.max(canvas.width, canvas.height) * 0.72); + const cx = canvas.width / 2; + const cy = canvas.height / 2; + const viewR = Math.min(canvas.width, canvas.height) / 2 - 3; + ctx.save(); + ctx.beginPath(); + ctx.arc(cx, cy, viewR, 0, Math.PI * 2); + ctx.clip(); + const bg = ctx.createRadialGradient(canvas.width * 0.52, canvas.height * 0.48, 10, cx, cy, viewR); bg.addColorStop(0, '#121a2b'); bg.addColorStop(0.55, '#070b16'); bg.addColorStop(1, '#02040a'); @@ -1235,6 +1244,14 @@ function _graphDraw() { } } + ctx.restore(); + ctx.restore(); + ctx.save(); + ctx.beginPath(); + ctx.arc(cx, cy, viewR, 0, Math.PI * 2); + ctx.strokeStyle = 'rgba(34, 211, 238, 0.18)'; + ctx.lineWidth = 1.2; + ctx.stroke(); ctx.restore(); const loaded = graphState.totalEngrams ? ` | engrams=${graphState.loadedEngrams}/${graphState.totalEngrams}` : ''; hint.textContent = `nodes=${graphState.nodes.length} edges=${graphState.edges.length}${loaded}` + (term ? ` | match=${matches}` : ''); @@ -1272,19 +1289,23 @@ function resetGraphView() { function fitGraphView(opts = {}) { const canvas = _graphCanvas(); if (!graphState.sim.length) return; - let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity; + let sumX = 0, sumY = 0; for (const n of graphState.sim) { - minX = Math.min(minX, n.x); minY = Math.min(minY, n.y); - maxX = Math.max(maxX, n.x); maxY = Math.max(maxY, n.y); + sumX += n.x; + sumY += n.y; + } + const centerX = sumX / graphState.sim.length; + const centerY = sumY / graphState.sim.length; + let radius = 1; + for (const n of graphState.sim) { + const dx = n.x - centerX; + const dy = n.y - centerY; + radius = Math.max(radius, Math.sqrt(dx * dx + dy * dy)); } - const w = Math.max(1, maxX - minX); - const h = Math.max(1, maxY - minY); const pad = graphState.sim.length > 20000 ? 96 : 54; - const zx = (canvas.width - pad) / w; - const zy = (canvas.height - pad) / h; - graphState.zoom = Math.max(0.04, Math.min(2.5, Math.min(zx, zy))); - graphState.panX = canvas.width / 2 - ((minX + maxX) / 2) * graphState.zoom; - graphState.panY = canvas.height / 2 - ((minY + maxY) / 2) * graphState.zoom; + graphState.zoom = Math.max(0.04, Math.min(2.5, (Math.min(canvas.width, canvas.height) - pad) / (radius * 2))); + graphState.panX = canvas.width / 2 - centerX * graphState.zoom; + graphState.panY = canvas.height / 2 - centerY * graphState.zoom; if (!opts.silent) _graphDraw(); }