proper connection routing!

This commit is contained in:
2025-09-08 11:57:12 -05:00
parent 3aefd7ef8c
commit fa738257e8
3 changed files with 145 additions and 31 deletions
+18 -7
View File
@@ -9,14 +9,25 @@
<link rel="stylesheet" href="style.css"> <link rel="stylesheet" href="style.css">
</head> </head>
<body> <body>
<div id="simContainer"></div> <div id="rowFlex" style="display: flex; flex-direction: row; align-items: start; justify-content: center; width: 100%; height: 100%;">
<div id="controlContainer"> <div id="propertyViewer">
<div id="controlButtons">
<button id="addNode" class="controlButton">Add Node</button>
</div> </div>
<div id="simToggles"> <div id="centerColumn" style="display: flex; flex-direction: column; align-items: center; justify-content: center; width: 70%; height: 100%; margin: 0 auto;">
<input type="checkbox" id="showRanges" class="simToggle"> <div id="simContainer"></div>
<label for="showRanges">Show Ranges</label> <div id="controlContainer">
<div id="controlButtons">
<button id="addNode" class="controlButton">Add Node</button>
</div>
<div id="simToggles">
<input type="checkbox" id="showRanges" class="simToggle">
<label for="showRanges">Show Ranges</label>
</div>
</div>
</div>
<div id="connectionViewer">
<details id="incomingConnections"><summary>Incoming Connections</summary></details>
<details id="outgoingConnections"><summary>Outgoing Connections</summary></details>
</div> </div>
</div> </div>
<script type="text/javascript" src="script.js"></script> <script type="text/javascript" src="script.js"></script>
+117 -22
View File
@@ -1,5 +1,6 @@
var UPDATE_INTERVAL = 10; var UPDATE_INTERVAL = 10;
var CONNECTION_DISTANCE = 250; var CONNECTION_DISTANCE = 250;
var DROP_PENTALTY = 1.2;
var nodes = new vis.DataSet([ var nodes = new vis.DataSet([
]); ]);
@@ -41,9 +42,42 @@ var options = {
var network = new vis.Network(container, data, options); var network = new vis.Network(container, data, options);
let selectedNode = null; let selectedNode = null;
function nodeSelectUpdateHandler(properties) {
// get containers
const incomingTable = document.getElementById('incomingConnections');
const outgoingTable = document.getElementById('outgoingConnections');
// clear containers
let connectionElements = incomingTable.querySelectorAll('.connectionElement');
for (let connection of connectionElements) {
connection.remove();
}
connectionElements = outgoingTable.querySelectorAll('.connectionElement');
for (let connection of connectionElements) {
connection.remove();
}
try {
for (let connection of nodeTable[properties.nodes[0]].outgoing) {
const connectionElement = document.createElement('div');
connectionElement.classList.add('connectionElement');
connectionElement.innerHTML = connection;
outgoingTable.appendChild(connectionElement);
}
for (let connection of nodeTable[properties.nodes[0]].incoming) {
const connectionElement = document.createElement('div');
connectionElement.classList.add('connectionElement');
connectionElement.innerHTML = connection;
incomingTable.appendChild(connectionElement);
}
} catch (error) {
return;
}
}
network.on('selectNode', function(properties) { network.on('selectNode', function(properties) {
selectedNode = properties.nodes[0];
if (document.getElementById('showRanges').checked) { if (document.getElementById('showRanges').checked) {
selectedNode = properties.nodes[0];
const centerPoint = network.getPosition(properties.nodes[0]); const centerPoint = network.getPosition(properties.nodes[0]);
const centerPointDOM = network.canvasToDOM({x: centerPoint.x, y: centerPoint.y}); const centerPointDOM = network.canvasToDOM({x: centerPoint.x, y: centerPoint.y});
const edgePointDOM = network.canvasToDOM({x: centerPoint.x + CONNECTION_DISTANCE, y: centerPoint.y}); const edgePointDOM = network.canvasToDOM({x: centerPoint.x + CONNECTION_DISTANCE, y: centerPoint.y});
@@ -56,15 +90,21 @@ network.on('selectNode', function(properties) {
rangeVis.style.height = radius * 2 + 'px'; rangeVis.style.height = radius * 2 + 'px';
rangeVis.classList.add('rangeVis'); rangeVis.classList.add('rangeVis');
document.getElementById('simContainer').appendChild(rangeVis); document.getElementById('simContainer').appendChild(rangeVis);
} else {
const rangeVis = document.querySelector('.rangeVis');
if (rangeVis) {
rangeVis.remove();
}
} }
nodeSelectUpdateHandler(properties);
}); });
network.on('dragging', function(properties) { network.on('dragging', function(properties) {
if (properties.nodes.length > 0) {
selectedNode = properties.nodes[0];
}
if (document.getElementById('showRanges').checked) { if (document.getElementById('showRanges').checked) {
if (selectedNode != null) { if (selectedNode != null) {
if (properties.nodes.length > 0) {
selectedNode = properties.nodes[0];
}
const centerPoint = network.getPosition(selectedNode); const centerPoint = network.getPosition(selectedNode);
const centerPointDOM = network.canvasToDOM({x: centerPoint.x, y: centerPoint.y}); const centerPointDOM = network.canvasToDOM({x: centerPoint.x, y: centerPoint.y});
const edgePointDOM = network.canvasToDOM({x: centerPoint.x + CONNECTION_DISTANCE, y: centerPoint.y}); const edgePointDOM = network.canvasToDOM({x: centerPoint.x + CONNECTION_DISTANCE, y: centerPoint.y});
@@ -100,6 +140,7 @@ network.on('dragging', function(properties) {
} }
} }
} }
nodeSelectUpdateHandler(properties);
}); });
network.on('zoom', function(properties) { network.on('zoom', function(properties) {
@@ -118,24 +159,24 @@ network.on('zoom', function(properties) {
} }
} }
} }
nodeSelectUpdateHandler(properties);
}); });
network.on('deselectNode', function(properties) { network.on('deselectNode', function(properties) {
selectedNode = null; selectedNode = null;
if (document.getElementById('showRanges').checked) { const rangeVis = document.querySelector('.rangeVis');
const rangeVis = document.querySelector('.rangeVis'); if (rangeVis) {
if (rangeVis) { rangeVis.remove();
rangeVis.remove();
}
} }
nodeSelectUpdateHandler(properties);
}); });
// main event loop // main event loop
function main() { function main() {
currentNodes = network.getPositions(); const currentNodes = network.getPositions();
currentNodesKeys = Object.keys(currentNodes); const currentNodesKeys = Object.keys(currentNodes);
for (let node1 of currentNodesKeys) { for (let node1 of currentNodesKeys) {
neighbors = []; let neighbors = [];
for (let node2 of currentNodesKeys) { for (let node2 of currentNodesKeys) {
if (node1 !== node2) { if (node1 !== node2) {
const node1data = currentNodes[node1]; const node1data = currentNodes[node1];
@@ -145,30 +186,84 @@ function main() {
neighbors.push({'node': node2, 'distance': distanceToRSSI(distance), }); neighbors.push({'node': node2, 'distance': distanceToRSSI(distance), });
} else { } else {
edges.remove(`${node1}->${node2}`); edges.remove(`${node1}->${node2}`);
nodeTable[node1].connections.splice(nodeTable[node1].connections.indexOf(node2), 1); const outIdx = nodeTable[node1].outgoing.indexOf(node2);
if (outIdx !== -1) {
nodeTable[node1].outgoing.splice(outIdx, 1);
}
const inIdx = nodeTable[node2].incoming.indexOf(node1);
if (inIdx !== -1) {
nodeTable[node2].incoming.splice(inIdx, 1);
}
} }
} }
} }
const scores = []; const scores = [];
for (let neighbor of neighbors) { for (let neighbor of neighbors) {
let connectionScore = 0; let connectionScore = 0;
let dropScore = 0;
connectionScore += (100 + neighbor.distance); connectionScore += (100 + neighbor.distance);
connectionScore += 100/nodeTable[neighbor.node].connections.length; connectionScore += 100 / (nodeTable[neighbor.node].incoming.length + 1);
if (nodeTable[neighbor.node].connections.length == 0) { if (nodeTable[neighbor.node].incoming.length == 0) {
connectionScore += 100; connectionScore += 10000;
} }
if (nodeTable[neighbor.node].connections.length > 3) { if (nodeTable[neighbor.node].incoming.length >= 3) {
continue; continue;
} }
scores.push({'node': neighbor.node, 'score': connectionScore}); if (nodeTable[node1].outgoing.includes(neighbor.node)) {
dropScore = connectionScore;
}
scores.push({'node': neighbor.node, 'score': connectionScore, 'dropScore': dropScore});
} }
scores.sort((a, b) => b.score - a.score); scores.sort((a, b) => b.score - a.score);
top3 = scores.slice(0, 3); const neighborMap = new Map(neighbors.map(n => [n.node, n]));
const top3 = scores.slice(0, 3);
for (let score of top3) { for (let score of top3) {
if (nodeTable[node1].connections.includes(score.node)) { if (nodeTable[node1].outgoing.length >= 3) {
let dropped = false;
if (nodeTable[node1].outgoing.includes(score.node)) {
//console.log(`${score.node} is already in outgoing; skipping drop attempt`);
continue;
}
for (let existingNode of [...nodeTable[node1].outgoing]) {
const neighborInfo = neighborMap.get(existingNode);
if (!neighborInfo) {
//console.log(`${existingNode} not a neighbor; skipping`);
continue;
}
let existingScore = 0;
existingScore += (100 + neighborInfo.distance);
existingScore += 100 / (nodeTable[existingNode].incoming.length + 1);
if (nodeTable[existingNode].incoming.length == 0) {
existingScore += 10000;
}
if (existingScore * DROP_PENTALTY < score.score) {
//console.log(`Dropping ${existingNode} from ${node1} for better ${score.node}`);
const outIdx = nodeTable[node1].outgoing.indexOf(existingNode);
if (outIdx !== -1) {
nodeTable[node1].outgoing.splice(outIdx, 1);
}
const inIdx = nodeTable[existingNode].incoming.indexOf(node1);
if (inIdx !== -1) {
nodeTable[existingNode].incoming.splice(inIdx, 1);
}
edges.remove(`${node1}->${existingNode}`);
dropped = true;
break;
} else {
//console.log(`Not dropping ${existingNode} from ${node1}; score ${existingScore.toFixed(2)} >= candidate ${score.score.toFixed(2)}`);
continue;
}
}
// still at capacity and nothing was dropped — skip adding this candidate
if (nodeTable[node1].outgoing.length >= 3 && !dropped) {
continue;
}
}
if (nodeTable[node1].outgoing.includes(score.node) || nodeTable[score.node].incoming.includes(node1)) {
continue; continue;
} }
nodeTable[node1].connections.push(score.node); nodeTable[node1].outgoing.push(score.node);
nodeTable[score.node].incoming.push(node1);
connectNodes(node1, score.node); connectNodes(node1, score.node);
} }
} }
@@ -179,5 +274,5 @@ main();
document.getElementById('addNode').addEventListener('click', function() { document.getElementById('addNode').addEventListener('click', function() {
const nodeId = generateRealisticLabel(); const nodeId = generateRealisticLabel();
nodes.add({id: nodeId, label: nodeId}); nodes.add({id: nodeId, label: nodeId});
nodeTable[nodeId] = {'connections': []}; nodeTable[nodeId] = {'incoming': [], 'outgoing': []};
}); });
+10 -2
View File
@@ -1,5 +1,5 @@
#simContainer { #simContainer {
width: 80%; width: 100%;
height: 90%; height: 90%;
margin: 0 auto; margin: 0 auto;
border: 1px solid #000; border: 1px solid #000;
@@ -10,7 +10,7 @@
} }
#controlContainer { #controlContainer {
width: 80%; width: 100%;
height: 10%; height: 10%;
margin: 0 auto; margin: 0 auto;
border: 1px solid #000; border: 1px solid #000;
@@ -23,6 +23,14 @@
align-items: center; align-items: center;
} }
#connectionViewer, #propertyViewer {
width: 15%;
height: 90%;
margin: 0 auto;
border: 1px solid #000;
padding: 5px;
}
.controlButton { .controlButton {
width: 100%; width: 100%;
height: 100%; height: 100%;