diff --git a/webapp/path_navigator.html b/webapp/path_navigator.html
index 5424406..1fb5a03 100644
--- a/webapp/path_navigator.html
+++ b/webapp/path_navigator.html
@@ -46,18 +46,23 @@
});
});
- const edgeList = edges.map(e => ({ s: e.source().id(), t: e.target().id() }));
+ const edgeList = edges.map(e => ({
+ s: e.source().id(),
+ t: e.target().id(),
+ isCrossChord: e.data('isCrossChord')
+ }));
const iter = (i) => {
state.forEach(s => s.fx = 0);
- edgeList.forEach(({s,t})=>{
+ edgeList.forEach(({s, t, isCrossChord})=>{
const a = state.get(s), b = state.get(t);
if(!a || !b) return;
const dx = b.x - a.x;
const dist = Math.abs(dx) || 0.0001;
const dir = dx / dist;
- const spring = opts.linkStrength * (dist - opts.linkDistance);
+ const strength = isCrossChord ? (opts.crossChordStrength || 0.001) : opts.linkStrength;
+ const spring = strength * (dist - opts.linkDistance);
a.fx += spring * dir;
b.fx -= spring * dir;
});
@@ -350,8 +355,8 @@
{
selector: 'edge',
style: {
- 'width': 1.5,
- 'line-color': '#555555',
+ 'width': 2.5,
+ 'line-color': '#ffffff',
'curve-style': 'straight',
'target-arrow-shape': 'none',
'label': 'data(ratio)',
@@ -364,6 +369,17 @@
'text-background-padding': '2px',
}
},
+ {
+ selector: 'edge[isCrossChord = "true"]',
+ style: {
+ 'width': 1,
+ 'line-color': '#aaaaaa',
+ 'line-style': 'dashed',
+ 'curve-style': 'straight',
+ 'target-arrow-shape': 'none',
+ 'label': '',
+ }
+ },
{
selector: ':selected',
style: {
@@ -479,6 +495,33 @@
// Build elements array for all chords
let elements = [];
+ // Collect cross-chord edges (same hs_array between adjacent chords)
+ const crossChordEdges = [];
+ for (let chordIdx = 1; chordIdx < allGraphsData.graphs.length; chordIdx++) {
+ const prevGraph = allGraphsData.graphs[chordIdx - 1];
+ const currGraph = allGraphsData.graphs[chordIdx];
+ if (!prevGraph || !prevGraph.nodes || !currGraph || !currGraph.nodes) continue;
+
+ currGraph.nodes.forEach(n => {
+ const prevNode = prevGraph.nodes.find(pn =>
+ JSON.stringify(pn.hs_array) === JSON.stringify(n.hs_array)
+ );
+ if (prevNode) {
+ const prevNodeId = `c${chordIdx - 1}_${prevNode.id}`;
+ const currNodeId = `c${chordIdx}_${n.id}`;
+ crossChordEdges.push({
+ group: 'edges',
+ data: {
+ source: prevNodeId,
+ target: currNodeId,
+ ratio: "1/1",
+ isCrossChord: "true"
+ },
+ });
+ }
+ });
+ }
+
allGraphsData.graphs.forEach((graph, chordIdx) => {
if (!graph || !graph.nodes) return;
@@ -527,6 +570,9 @@
if (elements.length === 0) return;
+ // Add cross-chord edges to elements BEFORE layout (so xforce considers them)
+ elements.push(...crossChordEdges);
+
// Add all elements
cy.add(elements);
console.log('Added', elements.length, 'elements');
@@ -544,16 +590,19 @@
});
// Run xforce layout to optimize x positions while keeping y fixed
- cy.layout({
+ const layout = cy.layout({
name: 'xforce',
linkDistance: 60,
linkStrength: 0.1,
+ crossChordStrength: 0.00005,
charge: -60,
collisionDistance: 35,
damping: 0.7,
iterations: 250,
bounds: bounds,
- }).run();
+ });
+
+ layout.run();
// Set canvas size
cy.style().json()[0].value = graphWidth;