+
+
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Incoming Connections
+ Outgoing Connections
diff --git a/script.js b/script.js
index cc3f861..72352c2 100644
--- a/script.js
+++ b/script.js
@@ -1,5 +1,6 @@
var UPDATE_INTERVAL = 10;
var CONNECTION_DISTANCE = 250;
+var DROP_PENTALTY = 1.2;
var nodes = new vis.DataSet([
]);
@@ -41,9 +42,42 @@ var options = {
var network = new vis.Network(container, data, options);
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) {
+ selectedNode = properties.nodes[0];
if (document.getElementById('showRanges').checked) {
- selectedNode = properties.nodes[0];
const centerPoint = network.getPosition(properties.nodes[0]);
const centerPointDOM = network.canvasToDOM({x: centerPoint.x, 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.classList.add('rangeVis');
document.getElementById('simContainer').appendChild(rangeVis);
+ } else {
+ const rangeVis = document.querySelector('.rangeVis');
+ if (rangeVis) {
+ rangeVis.remove();
+ }
}
+ nodeSelectUpdateHandler(properties);
});
network.on('dragging', function(properties) {
+ if (properties.nodes.length > 0) {
+ selectedNode = properties.nodes[0];
+ }
if (document.getElementById('showRanges').checked) {
if (selectedNode != null) {
- if (properties.nodes.length > 0) {
- selectedNode = properties.nodes[0];
- }
const centerPoint = network.getPosition(selectedNode);
const centerPointDOM = network.canvasToDOM({x: centerPoint.x, 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) {
@@ -118,24 +159,24 @@ network.on('zoom', function(properties) {
}
}
}
+ nodeSelectUpdateHandler(properties);
});
network.on('deselectNode', function(properties) {
selectedNode = null;
- if (document.getElementById('showRanges').checked) {
- const rangeVis = document.querySelector('.rangeVis');
- if (rangeVis) {
- rangeVis.remove();
- }
+ const rangeVis = document.querySelector('.rangeVis');
+ if (rangeVis) {
+ rangeVis.remove();
}
+ nodeSelectUpdateHandler(properties);
});
// main event loop
function main() {
- currentNodes = network.getPositions();
- currentNodesKeys = Object.keys(currentNodes);
+ const currentNodes = network.getPositions();
+ const currentNodesKeys = Object.keys(currentNodes);
for (let node1 of currentNodesKeys) {
- neighbors = [];
+ let neighbors = [];
for (let node2 of currentNodesKeys) {
if (node1 !== node2) {
const node1data = currentNodes[node1];
@@ -145,30 +186,84 @@ function main() {
neighbors.push({'node': node2, 'distance': distanceToRSSI(distance), });
} else {
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 = [];
for (let neighbor of neighbors) {
let connectionScore = 0;
+ let dropScore = 0;
connectionScore += (100 + neighbor.distance);
- connectionScore += 100/nodeTable[neighbor.node].connections.length;
- if (nodeTable[neighbor.node].connections.length == 0) {
- connectionScore += 100;
+ connectionScore += 100 / (nodeTable[neighbor.node].incoming.length + 1);
+ if (nodeTable[neighbor.node].incoming.length == 0) {
+ connectionScore += 10000;
}
- if (nodeTable[neighbor.node].connections.length > 3) {
+ if (nodeTable[neighbor.node].incoming.length >= 3) {
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);
- 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) {
- 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;
}
- nodeTable[node1].connections.push(score.node);
+ nodeTable[node1].outgoing.push(score.node);
+ nodeTable[score.node].incoming.push(node1);
connectNodes(node1, score.node);
}
}
@@ -179,5 +274,5 @@ main();
document.getElementById('addNode').addEventListener('click', function() {
const nodeId = generateRealisticLabel();
nodes.add({id: nodeId, label: nodeId});
- nodeTable[nodeId] = {'connections': []};
+ nodeTable[nodeId] = {'incoming': [], 'outgoing': []};
});
\ No newline at end of file
diff --git a/style.css b/style.css
index bf984eb..7906bd7 100644
--- a/style.css
+++ b/style.css
@@ -1,5 +1,5 @@
#simContainer {
- width: 80%;
+ width: 100%;
height: 90%;
margin: 0 auto;
border: 1px solid #000;
@@ -10,7 +10,7 @@
}
#controlContainer {
- width: 80%;
+ width: 100%;
height: 10%;
margin: 0 auto;
border: 1px solid #000;
@@ -23,6 +23,14 @@
align-items: center;
}
+#connectionViewer, #propertyViewer {
+ width: 15%;
+ height: 90%;
+ margin: 0 auto;
+ border: 1px solid #000;
+ padding: 5px;
+}
+
.controlButton {
width: 100%;
height: 100%;