update node move renderer to draw directly on canvas

This commit is contained in:
2025-11-13 09:58:55 -06:00
parent d4ed5524b1
commit d2292dcde7
2 changed files with 178 additions and 65 deletions
+148 -53
View File
@@ -12,6 +12,7 @@ class CompleteNodeOnEdgeEngine {
this.nodes = nodes;
this.edges = edges;
this.dotNodes = dotNodes;
this.movingDots = new Map();
this.forwTable = forwTable;
this.onArrival = null;
@@ -115,12 +116,16 @@ class CompleteNodeOnEdgeEngine {
this.network.on('beforeDrawing', (ctx) => {
if (this.edgesMoved) {
this.edgesMoved = false;
this.dotNodes.forEach((dotNode) => {
this.movingDots.forEach((dotNode) => {
this.fixDotOnEdge(dotNode);
});
}
});
this.network.on('afterDrawing', (ctx) => {
this.drawMovingDots(ctx);
});
this.network.on('dragging', (params) => {
if (params.edges.length <= 0) {
return;
@@ -136,8 +141,13 @@ class CompleteNodeOnEdgeEngine {
this.network.on('physicsMoving', (params) => {
this.edgesMoved = true;
this.edges.forEach((edge) => {
edge.pointsArr = {};
// Only clear cache for edges that have moving dots
// instead of all edges - more efficient for large networks
this.movingDots.forEach((dotNode) => {
const edgeItem = this.edges.get(dotNode.edge);
if (edgeItem) {
edgeItem.pointsArr = {};
}
});
});
@@ -157,39 +167,6 @@ class CompleteNodeOnEdgeEngine {
}
});
this.dotNodes.on('update', (event, properties, time) => {});
this.dotNodes.on('add', (event, properties, time) => {
properties.items.forEach((nodeId) => {
var node = this.dotNodes.get(nodeId);
if (this.traces_table[nodeId] === undefined || this.traces_table[nodeId].length == 0) {
this.traces_table[nodeId] = [node.source];
}
this.nodes.add(node);
});
});
this.dotNodes.on('remove', (event, properties, time) => {
properties.oldData.forEach((node) => {
this.nodes.remove(node.id);
if (this.ratio_inc >= 0) {
var entry = this.del_log_table[time];
if (entry === undefined) {
this.del_log_table[time] = [[node, node.source, node.target]];
} else {
entry.push([node, node.source, node.target]);
}
} else {
var entry = this.add_log_table[time];
if (entry === undefined) {
this.add_log_table[time] = [[node, node.source, node.target]];
} else {
entry.push([node, node.source, node.target]);
}
}
});
});
this.timer = requestAnimationFrame(this.loop);
}
@@ -270,30 +247,145 @@ class CompleteNodeOnEdgeEngine {
* @param {number} time - The current time of the movement.
*/
moveDot(current_ratio, time) {
if (this.dotNodes.length <= 0) {
if (this.movingDots.size <= 0) {
return;
}
this.dotNodes.forEach((dotNode) => {
const dotsToRemove = [];
this.movingDots.forEach((dotNode) => {
dotNode.ratio += current_ratio;
if (dotNode.ratio > 100) {
this.traces_table[dotNode.id].push(dotNode.target);
if (typeof this.onArrival === 'function') {
try {
this.onArrival({ from: dotNode.source, to: dotNode.target, dot: dotNode });
} catch (e) {}
this.dotNodes.remove(dotNode, time);
return;
}
} else if (dotNode.ratio < 0) {
dotsToRemove.push(dotNode);
return;
}
if (dotNode.ratio < 0) {
this.traces_table[dotNode.id].pop();
if (!this.updateDotNode(dotNode, this.traces_table[dotNode.id][this.traces_table[dotNode.id].length - 1], dotNode.source, 100 + dotNode.ratio)) {
this.dotNodes.remove(dotNode, time);
const previousHop = this.traces_table[dotNode.id][this.traces_table[dotNode.id].length - 1];
if (!this.updateDotNode(dotNode, previousHop, dotNode.source, 100 + dotNode.ratio)) {
dotsToRemove.push(dotNode);
return;
}
}
this.fixDotOnEdge(dotNode);
});
this.network.redraw();
dotsToRemove.forEach((dotNode) => {
this.removeMovingDot(dotNode, time);
});
// Request canvas redraw without forcing full network re-render
if (this.movingDots.size > 0) {
this.network.canvas.body.emitter.emit("_requestRedraw");
}
}
addMovingDot(dotNode, time = 0) {
if (!dotNode || dotNode.id === undefined) {
return;
}
if (!Array.isArray(this.traces_table[dotNode.id]) || this.traces_table[dotNode.id].length === 0) {
this.traces_table[dotNode.id] = [dotNode.source];
}
this.movingDots.set(dotNode.id, dotNode);
// Skip DataSet update - dots are drawn directly on canvas for better performance
// if (this.dotNodes && typeof this.dotNodes.update === 'function') {
// this.dotNodes.update(dotNode);
// }
}
removeMovingDot(dotNode, time = this.currentTime) {
if (!dotNode || dotNode.id === undefined) {
return;
}
this.movingDots.delete(dotNode.id);
// Skip DataSet removal - dots are drawn directly on canvas for better performance
// if (this.dotNodes && typeof this.dotNodes.remove === 'function') {
// this.dotNodes.remove(dotNode.id);
// }
const snapshot = {
...dotNode,
color: dotNode.color ? { ...dotNode.color } : undefined,
font: dotNode.font ? { ...dotNode.font } : undefined,
};
if (this.ratio_inc >= 0) {
var removeEntry = this.del_log_table[time];
if (removeEntry === undefined) {
this.del_log_table[time] = [[snapshot, dotNode.source, dotNode.target]];
} else {
removeEntry.push([snapshot, dotNode.source, dotNode.target]);
}
} else {
var addEntry = this.add_log_table[time];
if (addEntry === undefined) {
this.add_log_table[time] = [[snapshot, dotNode.source, dotNode.target]];
} else {
addEntry.push([snapshot, dotNode.source, dotNode.target]);
}
}
}
drawMovingDots(ctx) {
if (!ctx || this.movingDots.size === 0) {
return;
}
this.movingDots.forEach((dotNode) => {
if (dotNode.x === undefined || dotNode.y === undefined) {
return;
}
const radius = (dotNode.size || 10) / 2;
const fillColor = dotNode.color && dotNode.color.background ? dotNode.color.background : '#97c2fc';
const strokeColor = dotNode.color && dotNode.color.border ? dotNode.color.border : '#2b7ce9';
const lineWidth = dotNode.color && dotNode.color.borderWidth ? dotNode.color.borderWidth : 2;
ctx.save();
ctx.beginPath();
ctx.lineWidth = lineWidth;
ctx.strokeStyle = strokeColor;
ctx.fillStyle = fillColor;
if (dotNode.shape === 'box') {
const half = radius;
ctx.rect(dotNode.x - half, dotNode.y - half, half * 2, half * 2);
ctx.fill();
ctx.stroke();
} else {
ctx.arc(dotNode.x, dotNode.y, radius, 0, 2 * Math.PI, false);
ctx.fill();
ctx.stroke();
}
ctx.closePath();
if (dotNode.label) {
const fontSize = dotNode.font && dotNode.font.size ? dotNode.font.size : 12;
const fontFace = dotNode.font && dotNode.font.face ? dotNode.font.face : 'arial';
ctx.font = `${fontSize}px ${fontFace}`;
ctx.fillStyle = dotNode.font && dotNode.font.color ? dotNode.font.color : '#000';
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.fillText(dotNode.label, dotNode.x, dotNode.y);
}
ctx.restore();
});
}
/**
@@ -307,7 +399,7 @@ class CompleteNodeOnEdgeEngine {
var path = this.getEdgeConnectingNodes(dotNode.source, dotNode.target);
if (path[0] === undefined) {
// No longer a direct path; remove the moving dot gracefully
this.dotNodes.remove(dotNode);
this.removeMovingDot(dotNode);
return;
}
dotNode.edge = path[0];
@@ -325,7 +417,7 @@ class CompleteNodeOnEdgeEngine {
// Edge was removed from DataSet; attempt to retarget
var retryPath = this.getEdgeConnectingNodes(dotNode.source, dotNode.target);
if (retryPath[0] === undefined) {
this.dotNodes.remove(dotNode);
this.removeMovingDot(dotNode);
return;
}
dotNode.edge = retryPath[0];
@@ -346,10 +438,8 @@ class CompleteNodeOnEdgeEngine {
edgePoint = edge.edgeType.getPoint(current_ratio / 100);
datasetEdge.pointsArr[current_ratio] = edgePoint;
}
if (this.network.body.nodes[dotNode.id]) {
this.network.body.nodes[dotNode.id].x = edgePoint.x;
this.network.body.nodes[dotNode.id].y = edgePoint.y;
}
dotNode.x = edgePoint.x;
dotNode.y = edgePoint.y;
}
/**
@@ -407,15 +497,20 @@ class CompleteNodeOnEdgeEngine {
var edge = this.network.body.edges[newDotNode.edge];
var current_ratio = newDotNode.reversed ? 100 - newDotNode.ratio : newDotNode.ratio;
var edgePoint = this.edges.get(edge.id).pointsArr[current_ratio];
var datasetEdge = this.edges.get(edge.id);
if (!datasetEdge.pointsArr) {
datasetEdge.pointsArr = {};
}
var edgePoint = datasetEdge.pointsArr[current_ratio];
if (edgePoint === undefined) {
edgePoint = edge.edgeType.getPoint(current_ratio / 100);
this.edges.get(edge.id).pointsArr[current_ratio] = edgePoint;
datasetEdge.pointsArr[current_ratio] = edgePoint;
}
newDotNode.x = edgePoint.x;
newDotNode.y = edgePoint.y;
this.dotNodes.add(newDotNode, time);
this.addMovingDot(newDotNode, time);
// Canvas will redraw on next animation frame
}
/**
+30 -12
View File
@@ -120,12 +120,18 @@ onEdgeEngine.setArrivalCallback(async ({ from, to, dot }) => {
await routePacket(to, dot.id.split('-')[4], dot.id);
return;
}
// Batch edge updates for better performance
if (document.getElementById('showTTL').checked) {
edges.update({id: `${from}->${to}`, color: {color: smoothColorTransition('#eb4034', '#40eb34', 0, TTL, ttl+1), highlight: smoothColorTransition('#eb4034', '#40eb34', 0, TTL, ttl+1)}});
edges.update({id: `${to}->${from}`, color: {color: smoothColorTransition('#eb4034', '#40eb34', 0, TTL, ttl+1), highlight: smoothColorTransition('#eb4034', '#40eb34', 0, TTL, ttl+1)}});
const edgeColor = smoothColorTransition('#eb4034', '#40eb34', 0, TTL, ttl+1);
edges.update([
{id: `${from}->${to}`, color: {color: edgeColor, highlight: edgeColor}},
{id: `${to}->${from}`, color: {color: edgeColor, highlight: edgeColor}}
]);
} else {
edges.update({id: `${from}->${to}`, color: {color: '#2b7ce9', highlight: '#2b7ce9'}});
edges.update({id: `${to}->${from}`, color: {color: '#2b7ce9', highlight: '#2b7ce9'}});
edges.update([
{id: `${from}->${to}`, color: {color: '#2b7ce9', highlight: '#2b7ce9'}},
{id: `${to}->${from}`, color: {color: '#2b7ce9', highlight: '#2b7ce9'}}
]);
}
if (nodeTable[to].packetCache.has(dot.id.split('-')[0])) {
return;
@@ -275,14 +281,20 @@ function quickRoute(startNode, packetInfo, fromNode) {
nodeTable[nodeId].packetCache.add(packetId);
nodeTable[nodeId].routingTable[originNode] = from;
// Batch updates for better performance
if (document.getElementById('showTTL').checked) {
nodes.update({id: nodeId, color: {background: smoothColorTransition('#eb4034', '#40eb34', 0, TTL, ttl)}});
edges.update({id: `${from}->${nodeId}`, color: {color: smoothColorTransition('#eb4034', '#40eb34', 0, TTL, ttl+1), highlight: smoothColorTransition('#eb4034', '#40eb34', 0, TTL, ttl)}});
edges.update({id: `${nodeId}->${from}`, color: {color: smoothColorTransition('#eb4034', '#40eb34', 0, TTL, ttl+1), highlight: smoothColorTransition('#eb4034', '#40eb34', 0, TTL, ttl)}});
const edgeColor = smoothColorTransition('#eb4034', '#40eb34', 0, TTL, ttl+1);
edges.update([
{id: `${from}->${nodeId}`, color: {color: edgeColor, highlight: edgeColor}},
{id: `${nodeId}->${from}`, color: {color: edgeColor, highlight: edgeColor}}
]);
} else {
nodes.update({id: nodeId, color: {background: '#97c2fc'}});
edges.update({id: `${from}->${nodeId}`, color: {color: '#2b7ce9', highlight: '#2b7ce9'}});
edges.update({id: `${nodeId}->${from}`, color: {color: '#2b7ce9', highlight: '#2b7ce9'}});
edges.update([
{id: `${from}->${nodeId}`, color: {color: '#2b7ce9', highlight: '#2b7ce9'}},
{id: `${nodeId}->${from}`, color: {color: '#2b7ce9', highlight: '#2b7ce9'}}
]);
}
const neighborsSnapshot = (nodeTable[nodeId]?.connections?.slice()) || [];
@@ -354,12 +366,18 @@ async function routePacket(currentNode, goalNode, packetInfo, first=false) {
font: {color: document.getElementById('darkMode').checked ? '#e0e0e0' : '#000'}
};
onEdgeEngine.createDotNode(movingNode, currentNode, nextNode);
// Batch edge updates for better performance
if (document.getElementById('showTTL').checked) {
edges.update({id: `${currentNode}->${nextNode}`, color: {color: smoothColorTransition('#eb4034', '#40eb34', 0, TTL, ttl+1), highlight: smoothColorTransition('#eb4034', '#40eb34', 0, TTL, ttl)}});
edges.update({id: `${nextNode}->${currentNode}`, color: {color: smoothColorTransition('#eb4034', '#40eb34', 0, TTL, ttl+1), highlight: smoothColorTransition('#eb4034', '#40eb34', 0, TTL, ttl)}});
const edgeColor = smoothColorTransition('#eb4034', '#40eb34', 0, TTL, ttl+1);
edges.update([
{id: `${currentNode}->${nextNode}`, color: {color: edgeColor, highlight: edgeColor}},
{id: `${nextNode}->${currentNode}`, color: {color: edgeColor, highlight: edgeColor}}
]);
} else {
edges.update({id: `${currentNode}->${nextNode}`, color: {color: 'yellow', highlight: 'yellow'}});
edges.update({id: `${nextNode}->${currentNode}`, color: {color: 'yellow', highlight: 'yellow'}});
edges.update([
{id: `${currentNode}->${nextNode}`, color: {color: 'yellow', highlight: 'yellow'}},
{id: `${nextNode}->${currentNode}`, color: {color: 'yellow', highlight: 'yellow'}}
]);
}
} else {
consoleLog(`No route found from ${currentNode} to ${goalNode} via ${nextNode}. Dropping packet.`);