239 Multi-Hop Network Simulator
Visualize Routing in Mesh Networks
239.1 Multi-Hop Network Routing Simulator
This interactive simulator demonstrates how multi-hop wireless networks discover and maintain routes between nodes. Multi-hop networking is essential in IoT deployments where direct communication between all devices is not possible due to distance limitations or obstacles.
NoteSimulation Overview
This visualization demonstrates key multi-hop networking concepts:
- Communication Range: How wireless range affects network connectivity
- Routing Algorithms: Compare shortest path, minimum hop, and load-balanced routing
- Path Discovery: Visualize packet traversal through intermediate nodes
- Fault Tolerance: See how networks adapt when nodes fail
TipHow to Use This Simulator
- Adjust the Communication Range slider to see how range affects network topology
- Select Source and Destination nodes from the dropdowns
- Choose a Routing Algorithm to compare different path selection strategies
- Click “Send Packet” to animate packet traversal along the computed path
- Click on any node to toggle its enabled/disabled state and observe path recalculation
- Watch the Path Metrics panel for hop count, distance, and latency estimates
Show code
{
// Load D3.js v7
const d3 = await require("d3@7");
// IEEE Color palette
const colors = {
navy: "#2C3E50",
teal: "#16A085",
orange: "#E67E22",
gray: "#7F8C8D",
lightGray: "#ECF0F1",
white: "#FFFFFF",
red: "#E74C3C",
green: "#27AE60",
purple: "#9B59B6",
yellow: "#F1C40F",
darkBlue: "#1a252f"
};
// Layout dimensions
const width = 800;
const height = 600;
const margin = { top: 80, right: 20, bottom: 60, left: 20 };
const canvasWidth = width - margin.left - margin.right;
const canvasHeight = height - margin.top - margin.bottom;
// Network configuration
const initialNodes = [
{ id: 0, x: 80, y: 120, label: "Gateway", enabled: true, load: 0 },
{ id: 1, x: 200, y: 80, label: "Node 1", enabled: true, load: 0 },
{ id: 2, x: 320, y: 150, label: "Node 2", enabled: true, load: 0 },
{ id: 3, x: 180, y: 220, label: "Node 3", enabled: true, load: 0 },
{ id: 4, x: 400, y: 80, label: "Node 4", enabled: true, load: 0 },
{ id: 5, x: 480, y: 180, label: "Node 5", enabled: true, load: 0 },
{ id: 6, x: 350, y: 280, label: "Node 6", enabled: true, load: 0 },
{ id: 7, x: 550, y: 300, label: "Node 7", enabled: true, load: 0 },
{ id: 8, x: 650, y: 150, label: "Node 8", enabled: true, load: 0 },
{ id: 9, x: 620, y: 350, label: "Sensor 9", enabled: true, load: 0 }
];
// State variables
let nodes = JSON.parse(JSON.stringify(initialNodes));
let links = [];
let commRange = 150;
let sourceNode = 9;
let destNode = 0;
let routingAlgorithm = "shortest";
let currentPath = [];
let isAnimating = false;
let packetPosition = null;
// Create container
const container = d3.create("div")
.style("font-family", "system-ui, -apple-system, sans-serif");
// Control panel
const controls = container.append("div")
.style("display", "flex")
.style("flex-wrap", "wrap")
.style("gap", "15px")
.style("margin-bottom", "15px")
.style("padding", "15px")
.style("background", colors.lightGray)
.style("border-radius", "8px")
.style("align-items", "center");
// Range slider
const rangeGroup = controls.append("div")
.style("display", "flex")
.style("align-items", "center")
.style("gap", "8px");
rangeGroup.append("label")
.style("font-size", "12px")
.style("font-weight", "bold")
.style("color", colors.navy)
.text("Range (m):");
const rangeSlider = rangeGroup.append("input")
.attr("type", "range")
.attr("min", "50")
.attr("max", "200")
.attr("step", "10")
.attr("value", commRange)
.style("width", "100px")
.style("cursor", "pointer")
.on("input", function() {
commRange = parseInt(this.value);
rangeValue.text(commRange + "m");
updateLinks();
findPath();
updateVisualization();
});
const rangeValue = rangeGroup.append("span")
.style("font-size", "12px")
.style("color", colors.navy)
.style("min-width", "40px")
.text(commRange + "m");
// Source selector
const sourceGroup = controls.append("div")
.style("display", "flex")
.style("align-items", "center")
.style("gap", "8px");
sourceGroup.append("label")
.style("font-size", "12px")
.style("font-weight", "bold")
.style("color", colors.navy)
.text("Source:");
const sourceSelect = sourceGroup.append("select")
.style("padding", "6px 10px")
.style("border-radius", "4px")
.style("border", `1px solid ${colors.gray}`)
.style("font-size", "12px")
.style("cursor", "pointer")
.on("change", function() {
sourceNode = parseInt(this.value);
findPath();
updateVisualization();
});
// Destination selector
const destGroup = controls.append("div")
.style("display", "flex")
.style("align-items", "center")
.style("gap", "8px");
destGroup.append("label")
.style("font-size", "12px")
.style("font-weight", "bold")
.style("color", colors.navy)
.text("Destination:");
const destSelect = destGroup.append("select")
.style("padding", "6px 10px")
.style("border-radius", "4px")
.style("border", `1px solid ${colors.gray}`)
.style("font-size", "12px")
.style("cursor", "pointer")
.on("change", function() {
destNode = parseInt(this.value);
findPath();
updateVisualization();
});
// Algorithm selector
const algoGroup = controls.append("div")
.style("display", "flex")
.style("align-items", "center")
.style("gap", "8px");
algoGroup.append("label")
.style("font-size", "12px")
.style("font-weight", "bold")
.style("color", colors.navy)
.text("Algorithm:");
const algoSelect = algoGroup.append("select")
.style("padding", "6px 10px")
.style("border-radius", "4px")
.style("border", `1px solid ${colors.gray}`)
.style("font-size", "12px")
.style("cursor", "pointer")
.on("change", function() {
routingAlgorithm = this.value;
findPath();
updateVisualization();
});
[
{ value: "shortest", label: "Shortest Path (Dijkstra)" },
{ value: "minhop", label: "Minimum Hop Count" },
{ value: "loadbalanced", label: "Load Balanced" }
].forEach(opt => {
algoSelect.append("option")
.attr("value", opt.value)
.text(opt.label);
});
// Send Packet button
const sendBtn = controls.append("button")
.style("padding", "10px 20px")
.style("background", colors.teal)
.style("color", colors.white)
.style("border", "none")
.style("border-radius", "4px")
.style("font-size", "13px")
.style("font-weight", "bold")
.style("cursor", "pointer")
.text("Send Packet")
.on("click", animatePacket)
.on("mouseover", function() {
if (!isAnimating) d3.select(this).style("background", colors.navy);
})
.on("mouseout", function() {
if (!isAnimating) d3.select(this).style("background", colors.teal);
});
// Reset button
const resetBtn = controls.append("button")
.style("padding", "10px 16px")
.style("background", colors.gray)
.style("color", colors.white)
.style("border", "none")
.style("border-radius", "4px")
.style("font-size", "13px")
.style("font-weight", "bold")
.style("cursor", "pointer")
.text("Reset Nodes")
.on("click", resetNodes)
.on("mouseover", function() {
d3.select(this).style("background", colors.navy);
})
.on("mouseout", function() {
d3.select(this).style("background", colors.gray);
});
// Create main layout container
const mainLayout = container.append("div")
.style("display", "flex")
.style("gap", "15px")
.style("flex-wrap", "wrap");
// SVG container
const svgContainer = mainLayout.append("div")
.style("flex", "1")
.style("min-width", "500px");
// Create SVG canvas
const svg = svgContainer.append("svg")
.attr("viewBox", `0 0 ${width} ${height}`)
.attr("width", "100%")
.attr("height", height)
.style("background", "linear-gradient(180deg, #f8f9fa 0%, #e9ecef 100%)")
.style("border-radius", "8px")
.style("border", `2px solid ${colors.lightGray}`);
// Title
svg.append("text")
.attr("x", width / 2)
.attr("y", 30)
.attr("text-anchor", "middle")
.attr("font-size", "18px")
.attr("font-weight", "bold")
.attr("fill", colors.navy)
.text("Multi-Hop Wireless Network Simulator");
svg.append("text")
.attr("x", width / 2)
.attr("y", 50)
.attr("text-anchor", "middle")
.attr("font-size", "12px")
.attr("fill", colors.gray)
.text("Click nodes to toggle enabled/disabled state");
// Create canvas group
const canvas = svg.append("g")
.attr("transform", `translate(${margin.left}, ${margin.top})`);
// Link group (drawn first, below nodes)
const linkGroup = canvas.append("g").attr("class", "links");
// Range circle group
const rangeGroup2 = canvas.append("g").attr("class", "ranges");
// Path highlight group
const pathGroup = canvas.append("g").attr("class", "path");
// Node group
const nodeGroup = canvas.append("g").attr("class", "nodes");
// Packet group (on top)
const packetGroup = canvas.append("g").attr("class", "packet");
// Metrics panel
const metricsPanel = mainLayout.append("div")
.style("width", "220px")
.style("padding", "15px")
.style("background", colors.white)
.style("border-radius", "8px")
.style("border", `2px solid ${colors.lightGray}`)
.style("box-shadow", "0 2px 8px rgba(0,0,0,0.1)");
metricsPanel.append("h3")
.style("margin", "0 0 15px 0")
.style("color", colors.navy)
.style("font-size", "14px")
.style("border-bottom", `2px solid ${colors.teal}`)
.style("padding-bottom", "8px")
.text("Path Metrics");
const metricsContent = metricsPanel.append("div")
.attr("id", "metrics-content");
// Legend
const legend = svg.append("g")
.attr("transform", `translate(${margin.left}, ${height - 45})`);
const legendItems = [
{ color: colors.teal, label: "Active Node" },
{ color: colors.red, label: "Disabled Node" },
{ color: colors.orange, label: "Path Link" },
{ color: colors.lightGray, label: "Available Link" }
];
legendItems.forEach((item, i) => {
const g = legend.append("g")
.attr("transform", `translate(${i * 150}, 0)`);
g.append("circle")
.attr("cx", 8)
.attr("cy", 8)
.attr("r", 8)
.attr("fill", item.color);
g.append("text")
.attr("x", 22)
.attr("y", 12)
.attr("font-size", "11px")
.attr("fill", colors.navy)
.text(item.label);
});
// Populate node selectors
function populateSelectors() {
sourceSelect.selectAll("option").remove();
destSelect.selectAll("option").remove();
nodes.forEach(node => {
sourceSelect.append("option")
.attr("value", node.id)
.attr("selected", node.id === sourceNode ? true : null)
.text(node.label);
destSelect.append("option")
.attr("value", node.id)
.attr("selected", node.id === destNode ? true : null)
.text(node.label);
});
}
// Calculate distance between two nodes
function distance(n1, n2) {
return Math.sqrt(Math.pow(n2.x - n1.x, 2) + Math.pow(n2.y - n1.y, 2));
}
// Update links based on communication range
function updateLinks() {
links = [];
for (let i = 0; i < nodes.length; i++) {
for (let j = i + 1; j < nodes.length; j++) {
const dist = distance(nodes[i], nodes[j]);
if (dist <= commRange) {
links.push({
source: nodes[i],
target: nodes[j],
distance: dist
});
}
}
}
}
// Build adjacency list for enabled nodes
function buildGraph() {
const graph = {};
nodes.forEach(n => {
if (n.enabled) {
graph[n.id] = [];
}
});
links.forEach(link => {
if (link.source.enabled && link.target.enabled) {
graph[link.source.id].push({
node: link.target.id,
distance: link.distance,
load: nodes[link.target.id].load
});
graph[link.target.id].push({
node: link.source.id,
distance: link.distance,
load: nodes[link.source.id].load
});
}
});
return graph;
}
// Dijkstra's algorithm with different cost functions
function findPath() {
currentPath = [];
const graph = buildGraph();
if (!nodes[sourceNode].enabled || !nodes[destNode].enabled) {
return;
}
const distances = {};
const previous = {};
const visited = new Set();
const queue = [];
nodes.forEach(n => {
distances[n.id] = Infinity;
previous[n.id] = null;
});
distances[sourceNode] = 0;
queue.push({ node: sourceNode, cost: 0 });
while (queue.length > 0) {
// Sort by cost and get minimum
queue.sort((a, b) => a.cost - b.cost);
const current = queue.shift();
if (visited.has(current.node)) continue;
visited.add(current.node);
if (current.node === destNode) break;
if (!graph[current.node]) continue;
graph[current.node].forEach(neighbor => {
if (visited.has(neighbor.node)) return;
let cost;
switch (routingAlgorithm) {
case "shortest":
cost = neighbor.distance;
break;
case "minhop":
cost = 1; // Each hop costs 1
break;
case "loadbalanced":
cost = neighbor.distance * (1 + neighbor.load * 0.5);
break;
default:
cost = neighbor.distance;
}
const newDist = distances[current.node] + cost;
if (newDist < distances[neighbor.node]) {
distances[neighbor.node] = newDist;
previous[neighbor.node] = current.node;
queue.push({ node: neighbor.node, cost: newDist });
}
});
}
// Reconstruct path
if (distances[destNode] !== Infinity) {
let current = destNode;
while (current !== null) {
currentPath.unshift(current);
current = previous[current];
}
}
}
// Animate packet along path
function animatePacket() {
if (isAnimating || currentPath.length < 2) return;
isAnimating = true;
sendBtn.style("background", colors.gray).style("cursor", "not-allowed");
// Update load on path nodes
currentPath.forEach(nodeId => {
nodes[nodeId].load = Math.min(nodes[nodeId].load + 1, 5);
});
let hopIndex = 0;
// Create packet
const packet = packetGroup.append("circle")
.attr("r", 10)
.attr("fill", colors.orange)
.attr("stroke", colors.navy)
.attr("stroke-width", 2)
.attr("cx", nodes[currentPath[0]].x)
.attr("cy", nodes[currentPath[0]].y);
function nextHop() {
if (hopIndex >= currentPath.length - 1) {
// Animation complete
packet.transition()
.duration(300)
.attr("r", 15)
.attr("fill", colors.green)
.transition()
.duration(200)
.attr("r", 0)
.remove();
isAnimating = false;
sendBtn.style("background", colors.teal).style("cursor", "pointer");
updateVisualization();
return;
}
const fromNode = nodes[currentPath[hopIndex]];
const toNode = nodes[currentPath[hopIndex + 1]];
const dist = distance(fromNode, toNode);
const duration = Math.max(300, dist * 3);
// Highlight current hop
nodeGroup.selectAll("circle")
.filter(d => d.id === fromNode.id)
.transition()
.duration(100)
.attr("r", 25)
.transition()
.duration(100)
.attr("r", 20);
packet.transition()
.duration(duration)
.ease(d3.easeLinear)
.attr("cx", toNode.x)
.attr("cy", toNode.y)
.on("end", () => {
hopIndex++;
nextHop();
});
}
nextHop();
}
// Reset all nodes to enabled
function resetNodes() {
nodes.forEach(n => {
n.enabled = true;
n.load = 0;
});
findPath();
updateVisualization();
}
// Update metrics display
function updateMetrics() {
metricsContent.html("");
if (currentPath.length < 2) {
metricsContent.append("div")
.style("color", colors.red)
.style("font-size", "13px")
.style("padding", "10px")
.style("background", "#fde8e8")
.style("border-radius", "4px")
.text("No path available! Try enabling more nodes or increasing range.");
return;
}
// Calculate metrics
const hopCount = currentPath.length - 1;
let totalDistance = 0;
for (let i = 0; i < currentPath.length - 1; i++) {
totalDistance += distance(nodes[currentPath[i]], nodes[currentPath[i + 1]]);
}
// Estimate latency (assume 5ms base + 2ms per 10m)
const baseLatency = hopCount * 5;
const distanceLatency = totalDistance * 0.2;
const estimatedLatency = baseLatency + distanceLatency;
const metrics = [
{ label: "Hop Count", value: hopCount, unit: "hops", color: colors.teal },
{ label: "Total Distance", value: totalDistance.toFixed(1), unit: "m", color: colors.navy },
{ label: "Est. Latency", value: estimatedLatency.toFixed(1), unit: "ms", color: colors.orange },
{ label: "Algorithm", value: routingAlgorithm === "shortest" ? "Dijkstra" :
routingAlgorithm === "minhop" ? "Min Hop" : "Load Bal.",
unit: "", color: colors.purple }
];
metrics.forEach(m => {
const row = metricsContent.append("div")
.style("display", "flex")
.style("justify-content", "space-between")
.style("padding", "8px 0")
.style("border-bottom", `1px solid ${colors.lightGray}`);
row.append("span")
.style("font-size", "12px")
.style("color", colors.gray)
.text(m.label);
row.append("span")
.style("font-size", "13px")
.style("font-weight", "bold")
.style("color", m.color)
.text(`${m.value} ${m.unit}`);
});
// Show path
metricsContent.append("div")
.style("margin-top", "15px")
.style("padding-top", "10px")
.style("border-top", `2px solid ${colors.teal}`)
.append("div")
.style("font-size", "11px")
.style("font-weight", "bold")
.style("color", colors.navy)
.style("margin-bottom", "5px")
.text("Route Path:");
metricsContent.append("div")
.style("font-size", "11px")
.style("color", colors.teal)
.style("word-break", "break-word")
.text(currentPath.map(id => nodes[id].label).join(" → "));
}
// Main visualization update
function updateVisualization() {
// Update links
const linkSelection = linkGroup.selectAll("line")
.data(links, d => `${d.source.id}-${d.target.id}`);
linkSelection.exit().remove();
linkSelection.enter()
.append("line")
.merge(linkSelection)
.attr("x1", d => d.source.x)
.attr("y1", d => d.source.y)
.attr("x2", d => d.target.x)
.attr("y2", d => d.target.y)
.attr("stroke", d => {
// Check if this link is in the path
for (let i = 0; i < currentPath.length - 1; i++) {
if ((d.source.id === currentPath[i] && d.target.id === currentPath[i + 1]) ||
(d.target.id === currentPath[i] && d.source.id === currentPath[i + 1])) {
return colors.orange;
}
}
return d.source.enabled && d.target.enabled ? "#bdc3c7" : "#e8e8e8";
})
.attr("stroke-width", d => {
for (let i = 0; i < currentPath.length - 1; i++) {
if ((d.source.id === currentPath[i] && d.target.id === currentPath[i + 1]) ||
(d.target.id === currentPath[i] && d.source.id === currentPath[i + 1])) {
return 4;
}
}
return 2;
})
.attr("stroke-dasharray", d => d.source.enabled && d.target.enabled ? "none" : "5,5")
.attr("opacity", d => d.source.enabled && d.target.enabled ? 0.8 : 0.3);
// Update nodes
const nodeSelection = nodeGroup.selectAll("g.node")
.data(nodes, d => d.id);
nodeSelection.exit().remove();
const nodeEnter = nodeSelection.enter()
.append("g")
.attr("class", "node")
.style("cursor", "pointer")
.on("click", function(event, d) {
if (isAnimating) return;
d.enabled = !d.enabled;
findPath();
updateVisualization();
});
nodeEnter.append("circle")
.attr("r", 20);
nodeEnter.append("text")
.attr("text-anchor", "middle")
.attr("dy", "0.35em")
.attr("font-size", "10px")
.attr("font-weight", "bold")
.attr("fill", colors.white)
.attr("pointer-events", "none");
nodeEnter.append("text")
.attr("class", "label")
.attr("text-anchor", "middle")
.attr("dy", 35)
.attr("font-size", "10px")
.attr("fill", colors.navy)
.attr("pointer-events", "none");
// Update all nodes
const allNodes = nodeGroup.selectAll("g.node")
.attr("transform", d => `translate(${d.x}, ${d.y})`);
allNodes.select("circle")
.attr("fill", d => {
if (!d.enabled) return colors.red;
if (d.id === sourceNode) return colors.green;
if (d.id === destNode) return colors.purple;
if (currentPath.includes(d.id)) return colors.orange;
return colors.teal;
})
.attr("stroke", colors.navy)
.attr("stroke-width", d => (d.id === sourceNode || d.id === destNode) ? 3 : 2)
.attr("opacity", d => d.enabled ? 1 : 0.6);
allNodes.select("text:not(.label)")
.text(d => d.id);
allNodes.select("text.label")
.text(d => d.label);
// Update metrics
updateMetrics();
}
// Initialize
populateSelectors();
updateLinks();
findPath();
updateVisualization();
return container.node();
}239.2 Understanding Multi-Hop Routing
239.2.1 Routing Algorithm Comparison
| Algorithm | Optimization Goal | Best For | Trade-offs |
|---|---|---|---|
| Shortest Path | Minimize total distance | Energy efficiency | May use congested nodes |
| Minimum Hop | Minimize hop count | Low latency | May use longer links |
| Load Balanced | Distribute traffic | High throughput | Longer paths possible |
239.2.2 Key Concepts Demonstrated
ImportantCommunication Range Impact
The communication range slider demonstrates a critical trade-off: - Shorter range: More hops required, higher latency, but lower power per transmission - Longer range: Fewer hops, lower latency, but higher power consumption per transmission
239.2.3 Path Metrics Explained
- Hop Count: Number of intermediate nodes the packet must traverse
- Total Distance: Sum of all link distances in meters
- Estimated Latency: Based on processing delay per hop (5ms) plus propagation delay (0.2ms/m)
239.2.4 Real-World Applications
Multi-hop routing is essential in:
- Wireless Sensor Networks (WSN): Agricultural monitoring, environmental sensing
- Smart Cities: Distributed sensor networks for traffic, pollution monitoring
- Industrial IoT: Factory floor sensor networks with obstacles
- Emergency Networks: Ad-hoc networks where infrastructure is unavailable
239.3 Related Topics
- Multi-Hop Fundamentals - Theory behind multi-hop networking
- Ad-Hoc Network Routing - MANET routing protocols
- Wireless Sensor Networks - WSN architectures