From c6bb3a12ce9ac3cdb68e710e7e90b0eb21e18637 Mon Sep 17 00:00:00 2001 From: Michael Winter Date: Wed, 1 Apr 2026 16:37:45 +0200 Subject: [PATCH] Move graph computation from server to client --- webapp/path_navigator.html | 108 +++++++++++++++++++++++++++++++++++-- 1 file changed, 104 insertions(+), 4 deletions(-) diff --git a/webapp/path_navigator.html b/webapp/path_navigator.html index 987988d..d4854a4 100644 --- a/webapp/path_navigator.html +++ b/webapp/path_navigator.html @@ -578,12 +578,22 @@ }); } - // Load from Flask API - get ALL graphs at once + // Load from Flask API - get chords and compute graphs client-side async function loadAllGraphs() { try { - const response = await fetch("/api/all-graphs"); + const response = await fetch("/api/chords"); if (!response.ok) throw new Error("API not available"); - allGraphsData = await response.json(); + const data = await response.json(); + + // Compute graphs from raw chord data client-side + const graphs = data.chords.map((chord, index) => { + return calculateGraph(chord, index); + }); + + allGraphsData = { + total: data.total, + graphs: graphs + }; totalSteps = allGraphsData.total - 1; @@ -597,10 +607,100 @@ updateUI(); } catch (e) { - console.log("Error loading graphs:", e); + console.log("Error loading chords:", e); } } + // Compute graph (nodes + edges) from raw chord data - client-side version + function calculateGraph(chord, index) { + if (!chord) return { nodes: [], edges: [] }; + + const nodes = []; + const dims = [2, 3, 5, 7]; + + // Calculate cents for each pitch + for (let i = 0; i < chord.length; i++) { + const pitch = chord[i]; + const fraction = parseFraction(pitch.fraction || "1"); + const cents = fraction > 0 ? 1200 * Math.log2(fraction) : 0; + + nodes.push({ + id: i, + cents: Math.round(cents), + fraction: pitch.fraction || "1", + hs_array: pitch.hs_array || [] + }); + } + + // Find edges: differ by ±1 in exactly one dimension (ignoring dim 0) + const edges = []; + for (let i = 0; i < chord.length; i++) { + for (let j = i + 1; j < chord.length; j++) { + const hs1 = chord[i].hs_array || []; + const hs2 = chord[j].hs_array || []; + + if (!hs1.length || !hs2.length) continue; + + // Count differences in dims 1, 2, 3 + let diffCount = 0; + let diffDim = -1; + + for (let d = 1; d < hs1.length; d++) { + const diff = hs2[d] - hs1[d]; + if (Math.abs(diff) === 1) { + diffCount++; + diffDim = d; + } else if (diff !== 0) { + break; // diff > 1 in this dimension + } + } + + // Check if exactly one dimension differs + if (diffCount === 1 && diffDim > 0) { + // Calculate frequency ratio + const diffHs = []; + for (let d = 0; d < hs1.length; d++) { + diffHs.push(hs1[d] - hs2[d]); + } + + let numerator = 1; + let denominator = 1; + for (let dIdx = 0; dIdx < dims.length; dIdx++) { + const exp = diffHs[dIdx]; + if (exp > 0) { + numerator *= Math.pow(dims[dIdx], exp); + } else if (exp < 0) { + denominator *= Math.pow(dims[dIdx], -exp); + } + } + + const ratio = denominator > 1 ? `${numerator}/${denominator}` : `${numerator}`; + + edges.push({ + source: i, + target: j, + ratio: ratio, + dim: diffDim + }); + } + } + } + + return { nodes, edges, index }; + } + + // Parse fraction string to number + function parseFraction(fracStr) { + if (typeof fracStr === 'number') return fracStr; + if (!fracStr) return 1; + + if (fracStr.includes('/')) { + const [num, den] = fracStr.split('/').map(Number); + return num / den; + } + return Number(fracStr); + } + // Update UI elements function updateUI() { hasPrev = currentIndex > 0;