1161 Cellular IoT Technology Comparison Tool
Interactive Comparison of 2G, 3G, 4G LTE, LTE-M, NB-IoT, 5G NR, and 5G RedCap
1161.1 Cellular IoT Comparison Explorer
This interactive tool helps you compare cellular IoT technologies across multiple dimensions including data rates, latency, power consumption, coverage, and deployment costs. Use it to select the optimal technology for your IoT application.
This comprehensive comparison tool visualizes:
- Technology Profiles: Compare 7 cellular technologies side-by-side
- Radar Chart: Multi-dimensional performance visualization
- Evolution Timeline: Technology progression from 2G to 5G
- Use Case Matcher: Find the best technology for your application
- Power Profiles: PSM and eDRX configurations
- Sunset Timeline: 2G/3G deprecation schedules by region
- Cost Analysis: Module and deployment cost comparison
- Select technologies to compare using the checkboxes
- View the radar chart to see relative performance
- Use the use case matcher to find recommendations
- Check regional availability and sunset timelines
- Compare power profiles for battery-powered devices
- Review module costs for budget planning
Show code
// ============================================
// Cellular IoT Technology Comparison Tool
// Self-contained OJS implementation
// ============================================
{
// 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",
blue: "#3498DB",
darkGray: "#34495E"
};
// Technology data with comprehensive specifications
const technologies = {
"2G": {
name: "2G (GSM/GPRS)",
color: "#95A5A6",
year: 1991,
dataRateDown: 114, // kbps
dataRateUp: 14,
latency: 500, // ms
powerConsumption: 100, // relative scale 0-100
coverage: 70, // MCL relative
mobility: 90, // km/h support relative
voice: true,
deploymentCost: 20, // relative
moduleCost: 3, // USD
mcl: 144, // dB
peakCurrent: 1800, // mA
psm: false,
edrx: false,
sunset: { NA: 2022, EU: 2025, APAC: 2028 },
useCases: ["Legacy systems", "Voice fallback"],
status: "sunset"
},
"3G": {
name: "3G (UMTS/HSPA)",
color: "#7F8C8D",
year: 2001,
dataRateDown: 42000, // kbps
dataRateUp: 5760,
latency: 100,
powerConsumption: 85,
coverage: 65,
mobility: 95,
voice: true,
deploymentCost: 30,
moduleCost: 5,
mcl: 140,
peakCurrent: 1500,
psm: false,
edrx: false,
sunset: { NA: 2024, EU: 2025, APAC: 2026 },
useCases: ["Legacy telematics", "Backup connectivity"],
status: "sunset"
},
"4G LTE": {
name: "4G LTE (Cat 1+)",
color: "#3498DB",
year: 2009,
dataRateDown: 10000,
dataRateUp: 5000,
latency: 30,
powerConsumption: 70,
coverage: 60,
mobility: 100,
voice: true,
deploymentCost: 50,
moduleCost: 12,
mcl: 141,
peakCurrent: 1200,
psm: false,
edrx: true,
sunset: { NA: 2035, EU: 2035, APAC: 2035 },
useCases: ["Video streaming", "High-speed telemetry", "Mobile broadband"],
status: "active"
},
"LTE-M": {
name: "LTE-M (Cat-M1)",
color: "#16A085",
year: 2016,
dataRateDown: 1000,
dataRateUp: 1000,
latency: 15,
powerConsumption: 35,
coverage: 75,
mobility: 85,
voice: true,
deploymentCost: 40,
moduleCost: 8,
mcl: 156,
peakCurrent: 500,
psm: true,
edrx: true,
sunset: { NA: 2035, EU: 2035, APAC: 2035 },
useCases: ["Asset tracking", "Wearables", "Fleet management", "Health monitors"],
status: "active"
},
"NB-IoT": {
name: "NB-IoT (Cat-NB1/NB2)",
color: "#9B59B6",
year: 2016,
dataRateDown: 127,
dataRateUp: 159,
latency: 1600,
powerConsumption: 20,
coverage: 95,
mobility: 20,
voice: false,
deploymentCost: 35,
moduleCost: 5,
mcl: 164,
peakCurrent: 220,
psm: true,
edrx: true,
sunset: { NA: 2035, EU: 2035, APAC: 2035 },
useCases: ["Smart metering", "Smart parking", "Agriculture", "Building sensors"],
status: "active"
},
"5G NR": {
name: "5G NR (eMBB)",
color: "#E67E22",
year: 2019,
dataRateDown: 1000000,
dataRateUp: 100000,
latency: 1,
powerConsumption: 90,
coverage: 50,
mobility: 100,
voice: true,
deploymentCost: 100,
moduleCost: 50,
mcl: 138,
peakCurrent: 2000,
psm: false,
edrx: false,
sunset: { NA: 2050, EU: 2050, APAC: 2050 },
useCases: ["AR/VR", "Autonomous vehicles", "Industrial automation", "Video streaming"],
status: "active"
},
"5G RedCap": {
name: "5G RedCap (NR-Light)",
color: "#E74C3C",
year: 2023,
dataRateDown: 150000,
dataRateUp: 50000,
latency: 5,
powerConsumption: 45,
coverage: 70,
mobility: 90,
voice: true,
deploymentCost: 60,
moduleCost: 20,
mcl: 148,
peakCurrent: 600,
psm: true,
edrx: true,
sunset: { NA: 2050, EU: 2050, APAC: 2050 },
useCases: ["Industrial sensors", "Wearables", "Video surveillance", "Smart grid"],
status: "active"
}
};
// Use case to technology mapping
const useCaseMapping = {
"Smart metering": { primary: "NB-IoT", secondary: "LTE-M", reason: "Low power, deep coverage, small payloads" },
"Asset tracking": { primary: "LTE-M", secondary: "NB-IoT", reason: "Mobility support, GPS integration" },
"Video streaming": { primary: "5G NR", secondary: "4G LTE", reason: "High bandwidth, low latency" },
"Smart parking": { primary: "NB-IoT", secondary: "LTE-M", reason: "Indoor coverage, low data rate" },
"Fleet management": { primary: "LTE-M", secondary: "4G LTE", reason: "Mobility, real-time tracking" },
"Industrial automation": { primary: "5G NR", secondary: "5G RedCap", reason: "Ultra-low latency, reliability" },
"Wearables": { primary: "LTE-M", secondary: "5G RedCap", reason: "Small form factor, voice support" },
"Agriculture": { primary: "NB-IoT", secondary: "LTE-M", reason: "Rural coverage, battery life" },
"Building automation": { primary: "NB-IoT", secondary: "LTE-M", reason: "Indoor penetration, low power" },
"Health monitoring": { primary: "LTE-M", secondary: "5G RedCap", reason: "Reliability, voice emergency" },
"Smart grid": { primary: "NB-IoT", secondary: "5G RedCap", reason: "Coverage, scalability" },
"Autonomous vehicles": { primary: "5G NR", secondary: "4G LTE", reason: "Ultra-low latency, high reliability" }
};
// Layout dimensions
const width = 1000, height = 1400;
// State
let selectedTechs = ["LTE-M", "NB-IoT", "5G RedCap"];
// Create container
const container = d3.create("div")
.style("font-family", "system-ui, -apple-system, sans-serif")
.style("max-width", "1000px")
.style("margin", "0 auto");
// Header
const header = container.append("div")
.style("background", `linear-gradient(135deg, ${colors.navy} 0%, #1a252f 100%)`)
.style("border-radius", "12px 12px 0 0")
.style("padding", "20px")
.style("color", colors.white);
header.append("h2")
.style("margin", "0 0 10px 0")
.style("font-size", "24px")
.text("Cellular IoT Technology Comparison");
header.append("p")
.style("margin", "0")
.style("opacity", "0.9")
.style("font-size", "14px")
.text("Compare cellular technologies for IoT deployments across performance, power, and cost dimensions");
// Technology selector
const selectorPanel = container.append("div")
.style("background", colors.lightGray)
.style("padding", "15px 20px")
.style("display", "flex")
.style("flex-wrap", "wrap")
.style("gap", "10px")
.style("align-items", "center");
selectorPanel.append("span")
.style("font-weight", "bold")
.style("color", colors.navy)
.style("margin-right", "10px")
.text("Select Technologies:");
const checkboxContainer = selectorPanel.append("div")
.style("display", "flex")
.style("flex-wrap", "wrap")
.style("gap", "15px");
Object.entries(technologies).forEach(([key, tech]) => {
const label = checkboxContainer.append("label")
.style("display", "flex")
.style("align-items", "center")
.style("gap", "5px")
.style("cursor", "pointer")
.style("padding", "5px 10px")
.style("background", colors.white)
.style("border-radius", "6px")
.style("border", `2px solid ${tech.color}`)
.style("font-size", "13px");
const checkbox = label.append("input")
.attr("type", "checkbox")
.attr("checked", selectedTechs.includes(key) ? true : null)
.style("cursor", "pointer")
.on("change", function() {
if (this.checked) {
if (!selectedTechs.includes(key)) selectedTechs.push(key);
} else {
selectedTechs = selectedTechs.filter(t => t !== key);
}
updateVisualizations();
});
label.append("span")
.style("color", tech.color)
.style("font-weight", "bold")
.text(key);
if (tech.status === "sunset") {
label.append("span")
.style("font-size", "10px")
.style("color", colors.red)
.style("margin-left", "3px")
.text("(sunset)");
}
});
// Main content area
const mainContent = container.append("div")
.style("background", colors.white)
.style("padding", "20px");
// Create SVG for visualizations
const svg = mainContent.append("svg")
.attr("viewBox", `0 0 ${width} ${height}`)
.attr("width", "100%")
.style("display", "block");
// Section 1: Radar Chart
const radarGroup = svg.append("g")
.attr("transform", "translate(250, 250)");
radarGroup.append("text")
.attr("x", 0)
.attr("y", -220)
.attr("text-anchor", "middle")
.attr("font-size", "18px")
.attr("font-weight", "bold")
.attr("fill", colors.navy)
.text("Multi-Dimensional Performance Comparison");
const radarRadius = 180;
const radarDimensions = [
{ name: "Data Rate", key: "dataRate", angle: 0 },
{ name: "Low Latency", key: "latency", angle: 60, inverse: true },
{ name: "Power Efficiency", key: "power", angle: 120, inverse: true },
{ name: "Coverage (MCL)", key: "coverage", angle: 180 },
{ name: "Mobility", key: "mobility", angle: 240 },
{ name: "Cost Efficiency", key: "cost", angle: 300, inverse: true }
];
// Draw radar grid
[0.2, 0.4, 0.6, 0.8, 1.0].forEach((level, i) => {
const r = radarRadius * level;
const points = radarDimensions.map((dim, j) => {
const angle = (dim.angle - 90) * Math.PI / 180;
return `${r * Math.cos(angle)},${r * Math.sin(angle)}`;
}).join(" ");
radarGroup.append("polygon")
.attr("points", points)
.attr("fill", "none")
.attr("stroke", colors.lightGray)
.attr("stroke-width", 1);
if (i === 4) {
radarGroup.append("text")
.attr("x", 5)
.attr("y", -r - 5)
.attr("font-size", "10px")
.attr("fill", colors.gray)
.text("100%");
}
});
// Draw radar axes
radarDimensions.forEach(dim => {
const angle = (dim.angle - 90) * Math.PI / 180;
const x = radarRadius * Math.cos(angle);
const y = radarRadius * Math.sin(angle);
radarGroup.append("line")
.attr("x1", 0)
.attr("y1", 0)
.attr("x2", x)
.attr("y2", y)
.attr("stroke", colors.lightGray)
.attr("stroke-width", 1);
radarGroup.append("text")
.attr("x", x * 1.15)
.attr("y", y * 1.15)
.attr("text-anchor", "middle")
.attr("dominant-baseline", "middle")
.attr("font-size", "11px")
.attr("font-weight", "bold")
.attr("fill", colors.navy)
.text(dim.name);
});
// Radar data polygons (will be updated)
const radarPolygons = radarGroup.append("g").attr("class", "radar-polygons");
// Section 2: Specifications Table
const tableGroup = svg.append("g")
.attr("transform", "translate(550, 30)");
tableGroup.append("text")
.attr("x", 200)
.attr("y", 20)
.attr("text-anchor", "middle")
.attr("font-size", "18px")
.attr("font-weight", "bold")
.attr("fill", colors.navy)
.text("Technical Specifications");
const tableContainer = tableGroup.append("g")
.attr("transform", "translate(0, 40)")
.attr("class", "spec-table");
// Section 3: Timeline
const timelineGroup = svg.append("g")
.attr("transform", "translate(50, 520)");
timelineGroup.append("text")
.attr("x", 450)
.attr("y", 0)
.attr("text-anchor", "middle")
.attr("font-size", "18px")
.attr("font-weight", "bold")
.attr("fill", colors.navy)
.text("Technology Evolution Timeline");
const timelineY = 60;
const timelineStart = 1990;
const timelineEnd = 2030;
const timelineWidth = 850;
const timelineScale = d3.scaleLinear()
.domain([timelineStart, timelineEnd])
.range([50, timelineWidth]);
// Timeline axis
timelineGroup.append("line")
.attr("x1", 50)
.attr("y1", timelineY)
.attr("x2", timelineWidth)
.attr("y2", timelineY)
.attr("stroke", colors.navy)
.attr("stroke-width", 3);
// Timeline ticks
for (let year = 1990; year <= 2030; year += 5) {
const x = timelineScale(year);
timelineGroup.append("line")
.attr("x1", x)
.attr("y1", timelineY - 5)
.attr("x2", x)
.attr("y2", timelineY + 5)
.attr("stroke", colors.navy)
.attr("stroke-width", 2);
timelineGroup.append("text")
.attr("x", x)
.attr("y", timelineY + 20)
.attr("text-anchor", "middle")
.attr("font-size", "11px")
.attr("fill", colors.gray)
.text(year);
}
// Timeline events
Object.entries(technologies).forEach(([key, tech], i) => {
const x = timelineScale(tech.year);
const yOffset = (i % 2 === 0 ? -40 : -70) - (Math.floor(i / 2) * 15);
timelineGroup.append("circle")
.attr("cx", x)
.attr("cy", timelineY)
.attr("r", 8)
.attr("fill", tech.color);
timelineGroup.append("line")
.attr("x1", x)
.attr("y1", timelineY - 8)
.attr("x2", x)
.attr("y2", timelineY + yOffset + 15)
.attr("stroke", tech.color)
.attr("stroke-width", 2);
timelineGroup.append("text")
.attr("x", x)
.attr("y", timelineY + yOffset + 10)
.attr("text-anchor", "middle")
.attr("font-size", "10px")
.attr("font-weight", "bold")
.attr("fill", tech.color)
.text(`${key} (${tech.year})`);
});
// Section 4: Use Case Matcher
const useCaseGroup = svg.append("g")
.attr("transform", "translate(50, 680)");
useCaseGroup.append("text")
.attr("x", 450)
.attr("y", 0)
.attr("text-anchor", "middle")
.attr("font-size", "18px")
.attr("font-weight", "bold")
.attr("fill", colors.navy)
.text("Use Case Technology Matcher");
const useCaseContainer = useCaseGroup.append("g")
.attr("transform", "translate(0, 30)")
.attr("class", "use-case-matcher");
// Draw use case cards
const useCaseEntries = Object.entries(useCaseMapping);
const cardsPerRow = 3;
const cardWidth = 280;
const cardHeight = 70;
const cardGap = 20;
useCaseEntries.forEach(([useCase, rec], i) => {
const row = Math.floor(i / cardsPerRow);
const col = i % cardsPerRow;
const x = col * (cardWidth + cardGap);
const y = row * (cardHeight + cardGap);
const card = useCaseContainer.append("g")
.attr("transform", `translate(${x}, ${y})`);
const primaryTech = technologies[rec.primary];
const secondaryTech = technologies[rec.secondary];
card.append("rect")
.attr("width", cardWidth)
.attr("height", cardHeight)
.attr("fill", colors.white)
.attr("stroke", primaryTech.color)
.attr("stroke-width", 2)
.attr("rx", 8);
card.append("text")
.attr("x", 10)
.attr("y", 20)
.attr("font-size", "12px")
.attr("font-weight", "bold")
.attr("fill", colors.navy)
.text(useCase);
card.append("circle")
.attr("cx", 20)
.attr("cy", 42)
.attr("r", 8)
.attr("fill", primaryTech.color);
card.append("text")
.attr("x", 35)
.attr("y", 46)
.attr("font-size", "11px")
.attr("fill", colors.navy)
.text(rec.primary);
card.append("circle")
.attr("cx", 110)
.attr("cy", 42)
.attr("r", 6)
.attr("fill", secondaryTech.color)
.attr("opacity", 0.7);
card.append("text")
.attr("x", 122)
.attr("y", 46)
.attr("font-size", "10px")
.attr("fill", colors.gray)
.text(`Alt: ${rec.secondary}`);
card.append("text")
.attr("x", 10)
.attr("y", 62)
.attr("font-size", "9px")
.attr("fill", colors.gray)
.text(rec.reason.substring(0, 40) + (rec.reason.length > 40 ? "..." : ""));
});
// Section 5: Power Profiles
const powerGroup = svg.append("g")
.attr("transform", "translate(50, 1020)");
powerGroup.append("text")
.attr("x", 450)
.attr("y", 0)
.attr("text-anchor", "middle")
.attr("font-size", "18px")
.attr("font-weight", "bold")
.attr("fill", colors.navy)
.text("Power Saving Modes Comparison");
// PSM and eDRX table
const powerTableY = 40;
const powerCols = ["Technology", "PSM", "eDRX", "Peak Current", "Sleep Current", "Battery Life"];
const colWidth = 140;
// Header row
powerCols.forEach((col, i) => {
powerGroup.append("rect")
.attr("x", i * colWidth)
.attr("y", powerTableY)
.attr("width", colWidth)
.attr("height", 30)
.attr("fill", colors.navy);
powerGroup.append("text")
.attr("x", i * colWidth + colWidth / 2)
.attr("y", powerTableY + 20)
.attr("text-anchor", "middle")
.attr("font-size", "11px")
.attr("font-weight", "bold")
.attr("fill", colors.white)
.text(col);
});
// Data rows
const powerTechs = ["LTE-M", "NB-IoT", "5G RedCap", "4G LTE", "5G NR"];
powerTechs.forEach((techKey, row) => {
const tech = technologies[techKey];
const y = powerTableY + 30 + row * 35;
const rowData = [
techKey,
tech.psm ? "Yes" : "No",
tech.edrx ? "Yes" : "No",
`${tech.peakCurrent} mA`,
tech.psm ? "3 uA" : "10 mA",
tech.psm ? "10+ years" : "1-2 years"
];
rowData.forEach((val, col) => {
powerGroup.append("rect")
.attr("x", col * colWidth)
.attr("y", y)
.attr("width", colWidth)
.attr("height", 35)
.attr("fill", row % 2 === 0 ? colors.lightGray : colors.white)
.attr("stroke", colors.lightGray)
.attr("stroke-width", 1);
let textColor = colors.navy;
if (col === 1 || col === 2) {
textColor = val === "Yes" ? colors.green : colors.red;
}
powerGroup.append("text")
.attr("x", col * colWidth + colWidth / 2)
.attr("y", y + 22)
.attr("text-anchor", "middle")
.attr("font-size", "11px")
.attr("font-weight", col === 0 ? "bold" : "normal")
.attr("fill", textColor)
.text(val);
});
});
// Section 6: Sunset Timeline
const sunsetGroup = svg.append("g")
.attr("transform", "translate(50, 1260)");
sunsetGroup.append("text")
.attr("x", 450)
.attr("y", 0)
.attr("text-anchor", "middle")
.attr("font-size", "18px")
.attr("font-weight", "bold")
.attr("fill", colors.navy)
.text("2G/3G Network Sunset Timeline");
const sunsetRegions = ["North America", "Europe", "Asia Pacific"];
const sunsetTechs = ["2G", "3G"];
const regionKeys = ["NA", "EU", "APAC"];
sunsetRegions.forEach((region, ri) => {
const x = 50 + ri * 300;
sunsetGroup.append("text")
.attr("x", x + 100)
.attr("y", 35)
.attr("text-anchor", "middle")
.attr("font-size", "14px")
.attr("font-weight", "bold")
.attr("fill", colors.navy)
.text(region);
sunsetTechs.forEach((tech, ti) => {
const techData = technologies[tech];
const year = techData.sunset[regionKeys[ri]];
const y = 55 + ti * 40;
const currentYear = 2024;
const isPast = year <= currentYear;
sunsetGroup.append("rect")
.attr("x", x)
.attr("y", y)
.attr("width", 200)
.attr("height", 30)
.attr("fill", isPast ? colors.red : colors.orange)
.attr("opacity", 0.2)
.attr("rx", 6);
sunsetGroup.append("text")
.attr("x", x + 10)
.attr("y", y + 20)
.attr("font-size", "12px")
.attr("fill", colors.navy)
.text(`${tech}: ${year}`);
sunsetGroup.append("text")
.attr("x", x + 150)
.attr("y", y + 20)
.attr("font-size", "11px")
.attr("fill", isPast ? colors.red : colors.orange)
.text(isPast ? "Shutdown" : "Planned");
});
});
// Legend for radar chart
const legendGroup = svg.append("g")
.attr("transform", "translate(50, 30)")
.attr("class", "radar-legend");
// Update function
function updateVisualizations() {
// Clear and redraw radar polygons
radarPolygons.selectAll("*").remove();
// Calculate normalized values for radar
function getNormalizedValue(tech, dim) {
const t = technologies[tech];
switch (dim.key) {
case "dataRate":
// Log scale for data rate (very wide range)
return Math.log10(Math.max(t.dataRateDown, 1)) / Math.log10(1000000);
case "latency":
// Inverse: lower is better
return 1 - Math.min(t.latency / 2000, 1);
case "power":
// Inverse: lower is better
return 1 - t.powerConsumption / 100;
case "coverage":
return t.coverage / 100;
case "mobility":
return t.mobility / 100;
case "cost":
// Inverse: lower is better
return 1 - t.deploymentCost / 100;
default:
return 0.5;
}
}
selectedTechs.forEach((techKey, idx) => {
const tech = technologies[techKey];
const points = radarDimensions.map(dim => {
const value = getNormalizedValue(techKey, dim);
const angle = (dim.angle - 90) * Math.PI / 180;
const r = radarRadius * value;
return `${r * Math.cos(angle)},${r * Math.sin(angle)}`;
}).join(" ");
radarPolygons.append("polygon")
.attr("points", points)
.attr("fill", tech.color)
.attr("fill-opacity", 0.2)
.attr("stroke", tech.color)
.attr("stroke-width", 2);
// Add dots at vertices
radarDimensions.forEach(dim => {
const value = getNormalizedValue(techKey, dim);
const angle = (dim.angle - 90) * Math.PI / 180;
const r = radarRadius * value;
radarPolygons.append("circle")
.attr("cx", r * Math.cos(angle))
.attr("cy", r * Math.sin(angle))
.attr("r", 5)
.attr("fill", tech.color);
});
});
// Update legend
legendGroup.selectAll("*").remove();
legendGroup.append("text")
.attr("x", 0)
.attr("y", 0)
.attr("font-size", "14px")
.attr("font-weight", "bold")
.attr("fill", colors.navy)
.text("Selected Technologies:");
selectedTechs.forEach((techKey, i) => {
const tech = technologies[techKey];
const y = 25 + i * 25;
legendGroup.append("rect")
.attr("x", 0)
.attr("y", y - 12)
.attr("width", 18)
.attr("height", 18)
.attr("fill", tech.color)
.attr("rx", 3);
legendGroup.append("text")
.attr("x", 25)
.attr("y", y)
.attr("font-size", "12px")
.attr("fill", colors.navy)
.text(tech.name);
});
// Update specifications table
tableContainer.selectAll("*").remove();
if (selectedTechs.length > 0) {
const specs = [
{ label: "Data Rate (Down)", format: t => t.dataRateDown >= 1000 ? `${(t.dataRateDown/1000).toFixed(0)} Mbps` : `${t.dataRateDown} kbps` },
{ label: "Data Rate (Up)", format: t => t.dataRateUp >= 1000 ? `${(t.dataRateUp/1000).toFixed(0)} Mbps` : `${t.dataRateUp} kbps` },
{ label: "Latency", format: t => `${t.latency} ms` },
{ label: "MCL (Coverage)", format: t => `${t.mcl} dB` },
{ label: "Module Cost", format: t => `$${t.moduleCost}` },
{ label: "Voice Support", format: t => t.voice ? "Yes" : "No" },
{ label: "PSM Support", format: t => t.psm ? "Yes" : "No" },
{ label: "Status", format: t => t.status === "active" ? "Active" : "Sunset" }
];
const tableColWidth = 100;
const tableRowHeight = 28;
// Header row
tableContainer.append("rect")
.attr("x", 0)
.attr("y", 0)
.attr("width", tableColWidth)
.attr("height", tableRowHeight)
.attr("fill", colors.navy);
tableContainer.append("text")
.attr("x", tableColWidth / 2)
.attr("y", tableRowHeight / 2 + 4)
.attr("text-anchor", "middle")
.attr("font-size", "10px")
.attr("font-weight", "bold")
.attr("fill", colors.white)
.text("Specification");
selectedTechs.forEach((techKey, i) => {
const tech = technologies[techKey];
const x = tableColWidth + i * tableColWidth;
tableContainer.append("rect")
.attr("x", x)
.attr("y", 0)
.attr("width", tableColWidth)
.attr("height", tableRowHeight)
.attr("fill", tech.color);
tableContainer.append("text")
.attr("x", x + tableColWidth / 2)
.attr("y", tableRowHeight / 2 + 4)
.attr("text-anchor", "middle")
.attr("font-size", "10px")
.attr("font-weight", "bold")
.attr("fill", colors.white)
.text(techKey);
});
// Data rows
specs.forEach((spec, ri) => {
const y = tableRowHeight + ri * tableRowHeight;
tableContainer.append("rect")
.attr("x", 0)
.attr("y", y)
.attr("width", tableColWidth)
.attr("height", tableRowHeight)
.attr("fill", ri % 2 === 0 ? colors.lightGray : colors.white);
tableContainer.append("text")
.attr("x", 5)
.attr("y", y + tableRowHeight / 2 + 4)
.attr("font-size", "10px")
.attr("fill", colors.navy)
.text(spec.label);
selectedTechs.forEach((techKey, ci) => {
const tech = technologies[techKey];
const x = tableColWidth + ci * tableColWidth;
const value = spec.format(tech);
tableContainer.append("rect")
.attr("x", x)
.attr("y", y)
.attr("width", tableColWidth)
.attr("height", tableRowHeight)
.attr("fill", ri % 2 === 0 ? colors.lightGray : colors.white)
.attr("stroke", colors.lightGray)
.attr("stroke-width", 1);
let textColor = colors.navy;
if (value === "Yes") textColor = colors.green;
if (value === "No") textColor = colors.red;
if (value === "Sunset") textColor = colors.orange;
tableContainer.append("text")
.attr("x", x + tableColWidth / 2)
.attr("y", y + tableRowHeight / 2 + 4)
.attr("text-anchor", "middle")
.attr("font-size", "10px")
.attr("fill", textColor)
.text(value);
});
});
}
}
// Initial render
updateVisualizations();
return container.node();
}1161.2 Technology Overview
1161.2.1 Cellular IoT Categories
| Category | Technologies | Typical Use Cases |
|---|---|---|
| Legacy | 2G, 3G | Being phased out globally |
| Broadband | 4G LTE, 5G NR | High-bandwidth applications |
| LPWA Cellular | LTE-M, NB-IoT | Battery-powered IoT sensors |
| Mid-tier 5G | 5G RedCap | Bridge between LPWA and eMBB |
1161.2.2 Key Selection Criteria
- Battery Life Priority: NB-IoT > LTE-M > 5G RedCap
- Mobility Required: LTE-M > 5G RedCap > NB-IoT
- High Bandwidth: 5G NR > 4G LTE > 5G RedCap
- Deep Indoor Coverage: NB-IoT > LTE-M > Others
- Low Latency: 5G NR > LTE-M > NB-IoT
1161.3 Power Saving Modes
1161.3.1 PSM (Power Saving Mode)
PSM allows devices to enter deep sleep for extended periods:
- Sleep current: < 3 uA
- Wake period: Device-controlled
- Best for: Sensors reporting periodically (hourly/daily)
1161.3.2 eDRX (Extended Discontinuous Reception)
eDRX extends the paging cycle for reduced power:
- Cycle times: Up to 40+ minutes (NB-IoT)
- Reachability: Device remains reachable (unlike PSM)
- Best for: Applications needing occasional downlink
1161.4 Regional Considerations
Many regions are shutting down 2G and 3G networks:
- North America: 3G shutdown complete (AT&T 2022, Verizon 2022)
- Europe: Varies by country (2025-2028)
- Asia Pacific: Ongoing (2025-2030)
Plan new deployments with LTE-M, NB-IoT, or 5G technologies.
1161.5 Cost Comparison
| Technology | Module Cost | Data Plans | Best For |
|---|---|---|---|
| NB-IoT | $5-8 | Low-cost pooled | High-volume deployments |
| LTE-M | $8-15 | Mid-range | Mobile assets |
| 5G RedCap | $15-25 | Mid-range | Industrial IoT |
| 4G LTE | $12-20 | Standard | Existing infrastructure |
| 5G NR | $40-60 | Premium | High-performance |
1161.6 What’s Next
- NB-IoT Fundamentals - Deep dive into NB-IoT
- Cellular IoT Fundamentals - Complete cellular overview
- LTE-M vs NB-IoT - Detailed comparison
- 5G Advanced IoT - 5G NR and RedCap details
- LPWAN Introduction - All LPWAN technologies
This interactive tool is implemented in approximately 700 lines of Observable JavaScript. Key features:
- Multi-technology selector: Compare up to 7 technologies simultaneously
- Radar chart: 6-axis performance visualization with normalized scales
- Specifications table: Dynamic table based on selection
- Use case matcher: 12 common IoT applications mapped to optimal technologies
- Power profile comparison: PSM/eDRX support matrix
- Sunset timeline: Regional 2G/3G deprecation schedules
The tool uses the IEEE color palette for consistency: - Navy (#2C3E50): Primary UI elements - Teal (#16A085): LTE-M, good indicators - Purple (#9B59B6): NB-IoT - Orange (#E67E22): 5G NR, warnings - Red (#E74C3C): 5G RedCap, alerts