139 IoT Business Model Canvas
Interactive Tool for Designing IoT Business Models
139.1 Interactive Business Model Canvas
Design and visualize IoT business models using the Business Model Canvas framework. This interactive tool helps you structure your IoT venture across all nine building blocks, with IoT-specific templates, revenue calculations, and key metrics dashboards.
The Business Model Canvas is a strategic management template for developing new or documenting existing business models. This IoT-specific version includes:
- 9 Building Blocks: Value Proposition, Customer Segments, Channels, Customer Relationships, Revenue Streams, Key Resources, Key Activities, Key Partners, Cost Structure
- 4 IoT Business Model Templates: Product-as-a-Service, Data Monetization, Platform/Ecosystem, Outcome-based
- Revenue Stream Calculator: Model hardware, subscriptions, usage fees, and data monetization
- Cost Structure Estimator: Calculate COGS, infrastructure, and operational costs
- Key Metrics Dashboard: CAC, LTV, churn rate, and unit economics
- Export & Comparison: Save canvas as text, compare multiple models
- Select a template from the IoT business model types or start from scratch
- Click on each block to add items from IoT-specific suggestions or type custom entries
- Configure revenue streams with hardware, subscriptions, and usage pricing
- Set cost parameters to estimate your cost structure
- Review key metrics including CAC, LTV, and churn projections
- Export your canvas for presentations or compare multiple models
Show code
{
// ============================================
// IoT Business Model Canvas - Enhanced
// Interactive Business Planning Tool
// ============================================
const d3 = await require("d3@7");
// IEEE Color palette
const colors = {
navy: "#2C3E50",
teal: "#16A085",
orange: "#E67E22",
gray: "#7F8C8D",
lightGray: "#ECF0F1",
white: "#FFFFFF",
green: "#27AE60",
red: "#E74C3C",
darkGray: "#34495E",
purple: "#9B59B6",
blue: "#3498DB",
yellow: "#F1C40F",
cyan: "#00BCD4",
pink: "#E91E63"
};
// Block configurations with IoT-specific suggestions
const blocks = {
valueProposition: {
id: "valueProposition",
name: "Value Proposition",
shortName: "Value Prop",
color: colors.teal,
icon: "💎",
gridArea: "vp",
height: "medium",
suggestions: [
"Predictive maintenance alerts",
"Remote monitoring & control",
"Process automation",
"Energy cost reduction",
"Real-time visibility",
"Improved safety compliance",
"Reduced downtime",
"Data-driven insights",
"Operational efficiency",
"Customer experience enhancement",
"Asset utilization optimization",
"Quality control automation"
]
},
customerSegments: {
id: "customerSegments",
name: "Customer Segments",
shortName: "Customers",
color: colors.orange,
icon: "👥",
gridArea: "cs",
height: "medium",
suggestions: [
"Manufacturing enterprises",
"Healthcare providers",
"Smart building owners",
"Agricultural businesses",
"Logistics companies",
"Retail chains",
"Smart city governments",
"Energy utilities",
"Home consumers",
"Fleet operators",
"SMB owners",
"Industrial OEMs"
]
},
channels: {
id: "channels",
name: "Channels",
shortName: "Channels",
color: colors.blue,
icon: "📢",
gridArea: "ch",
height: "small",
suggestions: [
"Direct sales team",
"Partner resellers",
"Online marketplace",
"System integrators",
"OEM partnerships",
"App stores",
"Industry conferences",
"Digital marketing",
"Referral programs",
"Technical consultants"
]
},
customerRelationships: {
id: "customerRelationships",
name: "Customer Relationships",
shortName: "Relationships",
color: colors.purple,
icon: "🤝",
gridArea: "cr",
height: "small",
suggestions: [
"Dedicated support team",
"Self-service portal",
"Automated monitoring",
"Community forums",
"Success managers",
"Training programs",
"24/7 support center",
"Proactive maintenance",
"Co-creation partnerships",
"API developer support"
]
},
revenueStreams: {
id: "revenueStreams",
name: "Revenue Streams",
shortName: "Revenue",
color: colors.green,
icon: "💰",
gridArea: "rs",
height: "small",
suggestions: [
"Hardware sales",
"SaaS subscription",
"Data monetization",
"Usage-based pricing",
"Licensing fees",
"Professional services",
"Maintenance contracts",
"API access fees",
"Outcome-based pricing",
"Freemium + premium"
]
},
keyResources: {
id: "keyResources",
name: "Key Resources",
shortName: "Resources",
color: colors.navy,
icon: "🔧",
gridArea: "kr",
height: "small",
suggestions: [
"IoT sensor hardware",
"Cloud infrastructure",
"Analytics platform",
"Mobile applications",
"Data science team",
"IP & patents",
"Partner network",
"Customer data assets",
"Edge computing nodes",
"Security certifications"
]
},
keyActivities: {
id: "keyActivities",
name: "Key Activities",
shortName: "Activities",
color: colors.red,
icon: "⚡",
gridArea: "ka",
height: "small",
suggestions: [
"Hardware development",
"Software development",
"Data analytics",
"Platform operations",
"Customer onboarding",
"Security management",
"Partner management",
"Regulatory compliance",
"Continuous R&D",
"Market expansion"
]
},
keyPartners: {
id: "keyPartners",
name: "Key Partners",
shortName: "Partners",
color: colors.darkGray,
icon: "🏢",
gridArea: "kp",
height: "small",
suggestions: [
"Cloud providers (AWS/Azure/GCP)",
"Hardware manufacturers",
"Telecom operators",
"System integrators",
"Industry consultants",
"Certification bodies",
"Research institutions",
"Channel partners",
"Technology alliances",
"Data providers"
]
},
costStructure: {
id: "costStructure",
name: "Cost Structure",
shortName: "Costs",
color: colors.gray,
icon: "📊",
gridArea: "cost",
height: "small",
suggestions: [
"Cloud hosting costs",
"Hardware COGS",
"R&D salaries",
"Sales & marketing",
"Customer support",
"Partner commissions",
"Security & compliance",
"Data storage",
"Connectivity costs",
"Office & operations"
]
}
};
// IoT Business Model Templates
const templates = {
blank: {
name: "Start Fresh",
icon: "📝",
description: "Empty canvas",
data: {},
revenueModel: {
hardwarePrice: 0,
hardwareMargin: 30,
monthlySubscription: 0,
usageFee: 0,
dataRevenue: 0,
customersYear1: 100,
growthRate: 50,
churnRate: 5
},
costModel: {
hardwareCOGS: 70,
cloudCostPerUser: 5,
supportCostPerUser: 3,
cacCost: 500,
rdPercent: 20,
salesPercent: 25
}
},
productAsService: {
name: "Product-as-a-Service",
icon: "🔄",
description: "Hardware + recurring software",
data: {
valueProposition: ["Remote monitoring & control", "Predictive maintenance alerts", "Reduced downtime"],
customerSegments: ["Manufacturing enterprises", "Industrial OEMs"],
channels: ["Direct sales team", "System integrators"],
customerRelationships: ["Dedicated support team", "Success managers", "Proactive maintenance"],
revenueStreams: ["Hardware sales", "SaaS subscription", "Maintenance contracts"],
keyResources: ["IoT sensor hardware", "Cloud infrastructure", "Data science team"],
keyActivities: ["Hardware development", "Platform operations", "Customer onboarding"],
keyPartners: ["Hardware manufacturers", "Cloud providers (AWS/Azure/GCP)"],
costStructure: ["Hardware COGS", "Cloud hosting costs", "R&D salaries", "Customer support"]
},
revenueModel: {
hardwarePrice: 999,
hardwareMargin: 40,
monthlySubscription: 99,
usageFee: 0,
dataRevenue: 0,
customersYear1: 200,
growthRate: 60,
churnRate: 8
},
costModel: {
hardwareCOGS: 60,
cloudCostPerUser: 15,
supportCostPerUser: 10,
cacCost: 2000,
rdPercent: 25,
salesPercent: 20
}
},
dataMonetization: {
name: "Data Monetization",
icon: "📈",
description: "Value from aggregated data",
data: {
valueProposition: ["Data-driven insights", "Market intelligence", "Benchmarking reports"],
customerSegments: ["Retail chains", "Smart city governments", "Energy utilities"],
channels: ["Online marketplace", "Digital marketing", "Industry conferences"],
customerRelationships: ["Self-service portal", "API developer support", "Community forums"],
revenueStreams: ["Data monetization", "API access fees", "Licensing fees"],
keyResources: ["Customer data assets", "Analytics platform", "Data science team"],
keyActivities: ["Data analytics", "Security management", "Regulatory compliance"],
keyPartners: ["Data providers", "Research institutions", "Technology alliances"],
costStructure: ["Data storage", "R&D salaries", "Security & compliance"]
},
revenueModel: {
hardwarePrice: 199,
hardwareMargin: 20,
monthlySubscription: 29,
usageFee: 0,
dataRevenue: 50,
customersYear1: 5000,
growthRate: 80,
churnRate: 12
},
costModel: {
hardwareCOGS: 80,
cloudCostPerUser: 3,
supportCostPerUser: 1,
cacCost: 100,
rdPercent: 30,
salesPercent: 30
}
},
platformEcosystem: {
name: "Platform/Ecosystem",
icon: "🌐",
description: "Multi-sided marketplace",
data: {
valueProposition: ["Real-time visibility", "Process automation", "Customer experience enhancement"],
customerSegments: ["Smart building owners", "Home consumers", "SMB owners"],
channels: ["App stores", "Partner resellers", "Online marketplace"],
customerRelationships: ["Self-service portal", "Community forums", "Training programs"],
revenueStreams: ["Freemium + premium", "Usage-based pricing", "Professional services"],
keyResources: ["Mobile applications", "Partner network", "Cloud infrastructure"],
keyActivities: ["Software development", "Partner management", "Market expansion"],
keyPartners: ["Channel partners", "Technology alliances", "Hardware manufacturers"],
costStructure: ["Cloud hosting costs", "Sales & marketing", "Partner commissions"]
},
revenueModel: {
hardwarePrice: 0,
hardwareMargin: 0,
monthlySubscription: 19,
usageFee: 0.01,
dataRevenue: 0,
customersYear1: 10000,
growthRate: 100,
churnRate: 15
},
costModel: {
hardwareCOGS: 0,
cloudCostPerUser: 2,
supportCostPerUser: 0.5,
cacCost: 50,
rdPercent: 35,
salesPercent: 35
}
},
outcomeBased: {
name: "Outcome-Based",
icon: "🎯",
description: "Pay for results",
data: {
valueProposition: ["Energy cost reduction", "Operational efficiency", "Asset utilization optimization"],
customerSegments: ["Energy utilities", "Manufacturing enterprises", "Fleet operators"],
channels: ["Direct sales team", "Technical consultants", "Industry conferences"],
customerRelationships: ["Co-creation partnerships", "Dedicated support team", "Success managers"],
revenueStreams: ["Outcome-based pricing", "Professional services", "SaaS subscription"],
keyResources: ["Analytics platform", "Data science team", "IP & patents"],
keyActivities: ["Data analytics", "Continuous R&D", "Customer onboarding"],
keyPartners: ["System integrators", "Industry consultants", "Certification bodies"],
costStructure: ["R&D salaries", "Customer support", "Sales & marketing"]
},
revenueModel: {
hardwarePrice: 0,
hardwareMargin: 0,
monthlySubscription: 0,
usageFee: 0,
dataRevenue: 0,
customersYear1: 50,
growthRate: 40,
churnRate: 5
},
costModel: {
hardwareCOGS: 0,
cloudCostPerUser: 25,
supportCostPerUser: 50,
cacCost: 10000,
rdPercent: 30,
salesPercent: 20
}
}
};
// State
let canvasData = {
valueProposition: [],
customerSegments: [],
channels: [],
customerRelationships: [],
revenueStreams: [],
keyResources: [],
keyActivities: [],
keyPartners: [],
costStructure: []
};
let revenueModel = { ...templates.blank.revenueModel };
let costModel = { ...templates.blank.costModel };
let activeBlock = null;
let currentTemplate = "blank";
let savedModels = [];
// Create main container
const container = d3.create("div")
.style("font-family", "system-ui, -apple-system, sans-serif")
.style("max-width", "1200px")
.style("margin", "0 auto");
// Header with template selector
const header = container.append("div")
.style("display", "flex")
.style("justify-content", "space-between")
.style("align-items", "center")
.style("margin-bottom", "15px")
.style("padding", "15px")
.style("background", `linear-gradient(135deg, ${colors.navy} 0%, ${colors.darkGray} 100%)`)
.style("border-radius", "12px")
.style("color", colors.white)
.style("flex-wrap", "wrap")
.style("gap", "10px");
const titleDiv = header.append("div");
titleDiv.append("div")
.style("font-size", "20px")
.style("font-weight", "bold")
.text("IoT Business Model Canvas");
titleDiv.append("div")
.style("font-size", "11px")
.style("opacity", "0.8")
.text("Click blocks to edit | Select templates below");
// Template selector
const templateDiv = header.append("div")
.style("display", "flex")
.style("gap", "8px")
.style("flex-wrap", "wrap")
.style("align-items", "center");
Object.entries(templates).forEach(([key, template]) => {
templateDiv.append("button")
.attr("id", `template-${key}`)
.style("padding", "6px 12px")
.style("font-size", "10px")
.style("background", currentTemplate === key ? colors.teal : "rgba(255,255,255,0.2)")
.style("color", colors.white)
.style("border", `1px solid ${currentTemplate === key ? colors.teal : "rgba(255,255,255,0.3)"}`)
.style("border-radius", "15px")
.style("cursor", "pointer")
.style("transition", "all 0.2s ease")
.text(`${template.icon} ${template.name}`)
.attr("title", template.description)
.on("click", () => loadTemplate(key))
.on("mouseenter", function() {
if (currentTemplate !== key) {
d3.select(this).style("background", "rgba(255,255,255,0.3)");
}
})
.on("mouseleave", function() {
if (currentTemplate !== key) {
d3.select(this).style("background", "rgba(255,255,255,0.2)");
}
});
});
// Main layout - canvas and metrics
const mainLayout = container.append("div")
.style("display", "grid")
.style("grid-template-columns", "1fr 320px")
.style("gap", "15px");
// Left side - Canvas
const canvasContainer = mainLayout.append("div");
// Canvas grid layout (standard BMC layout)
const canvasGrid = canvasContainer.append("div")
.style("display", "grid")
.style("grid-template-columns", "1fr 1fr 1fr 1fr 1fr")
.style("grid-template-rows", "1fr 1fr 0.8fr")
.style("gap", "6px")
.style("grid-template-areas", `
"kp ka vp cr cs"
"kp ka vp ch cs"
"cost cost rs rs rs"
`);
// Create block elements
Object.entries(blocks).forEach(([key, block]) => {
const blockDiv = canvasGrid.append("div")
.attr("id", `block-${key}`)
.style("grid-area", block.gridArea)
.style("background", colors.white)
.style("border", `2px solid ${block.color}`)
.style("border-radius", "8px")
.style("padding", "8px")
.style("cursor", "pointer")
.style("transition", "all 0.2s ease")
.style("overflow", "hidden")
.on("click", () => openBlockEditor(key))
.on("mouseenter", function() {
d3.select(this).style("box-shadow", `0 4px 12px ${block.color}44`);
})
.on("mouseleave", function() {
d3.select(this).style("box-shadow", "none");
});
// Block header
const blockHeader = blockDiv.append("div")
.style("display", "flex")
.style("align-items", "center")
.style("gap", "4px")
.style("margin-bottom", "6px")
.style("padding-bottom", "4px")
.style("border-bottom", `2px solid ${block.color}33`);
blockHeader.append("span")
.style("font-size", "12px")
.text(block.icon);
blockHeader.append("span")
.style("font-size", "10px")
.style("font-weight", "bold")
.style("color", block.color)
.text(block.shortName);
// Item count badge
blockHeader.append("span")
.attr("id", `count-${key}`)
.style("font-size", "9px")
.style("background", block.color)
.style("color", colors.white)
.style("padding", "1px 5px")
.style("border-radius", "8px")
.style("margin-left", "auto")
.text("0");
// Items container
blockDiv.append("div")
.attr("id", `items-${key}`)
.style("font-size", "9px")
.style("color", colors.gray)
.style("max-height", "100px")
.style("overflow-y", "auto");
});
// Right side - Metrics and configuration
const rightPanel = mainLayout.append("div");
// Revenue Model Section
const revenueSection = rightPanel.append("div")
.style("background", colors.white)
.style("border", `2px solid ${colors.green}`)
.style("border-radius", "12px")
.style("padding", "12px")
.style("margin-bottom", "12px");
revenueSection.append("div")
.style("font-weight", "bold")
.style("color", colors.green)
.style("margin-bottom", "10px")
.style("font-size", "13px")
.text("💰 Revenue Model");
const revenueInputs = revenueSection.append("div")
.attr("id", "revenue-inputs");
function createInput(parent, label, id, value, prefix = "", suffix = "", step = "1") {
const row = parent.append("div")
.style("display", "flex")
.style("justify-content", "space-between")
.style("align-items", "center")
.style("margin-bottom", "6px");
row.append("label")
.style("font-size", "10px")
.style("color", colors.navy)
.text(label);
const inputWrapper = row.append("div")
.style("display", "flex")
.style("align-items", "center")
.style("gap", "2px");
if (prefix) {
inputWrapper.append("span")
.style("font-size", "10px")
.style("color", colors.gray)
.text(prefix);
}
inputWrapper.append("input")
.attr("type", "number")
.attr("id", id)
.attr("step", step)
.property("value", value)
.style("width", "65px")
.style("padding", "4px 6px")
.style("border", `1px solid ${colors.lightGray}`)
.style("border-radius", "4px")
.style("font-size", "10px")
.style("text-align", "right")
.on("input", updateMetrics);
if (suffix) {
inputWrapper.append("span")
.style("font-size", "10px")
.style("color", colors.gray)
.text(suffix);
}
}
createInput(revenueInputs, "Hardware Price", "hardware-price", revenueModel.hardwarePrice, "$");
createInput(revenueInputs, "Monthly Subscription", "monthly-sub", revenueModel.monthlySubscription, "$", "/mo");
createInput(revenueInputs, "Usage Fee (per tx)", "usage-fee", revenueModel.usageFee, "$", "", "0.01");
createInput(revenueInputs, "Data Revenue (per user/mo)", "data-revenue", revenueModel.dataRevenue, "$");
createInput(revenueInputs, "Year 1 Customers", "customers-y1", revenueModel.customersYear1);
createInput(revenueInputs, "Annual Growth Rate", "growth-rate", revenueModel.growthRate, "", "%");
createInput(revenueInputs, "Annual Churn Rate", "churn-rate", revenueModel.churnRate, "", "%");
// Cost Model Section
const costSection = rightPanel.append("div")
.style("background", colors.white)
.style("border", `2px solid ${colors.red}`)
.style("border-radius", "12px")
.style("padding", "12px")
.style("margin-bottom", "12px");
costSection.append("div")
.style("font-weight", "bold")
.style("color", colors.red)
.style("margin-bottom", "10px")
.style("font-size", "13px")
.text("📊 Cost Structure");
const costInputs = costSection.append("div")
.attr("id", "cost-inputs");
createInput(costInputs, "Hardware COGS", "hardware-cogs", costModel.hardwareCOGS, "", "%");
createInput(costInputs, "Cloud Cost/User/mo", "cloud-cost", costModel.cloudCostPerUser, "$");
createInput(costInputs, "Support Cost/User/mo", "support-cost", costModel.supportCostPerUser, "$");
createInput(costInputs, "Customer Acquisition Cost", "cac-cost", costModel.cacCost, "$");
createInput(costInputs, "R&D (% of revenue)", "rd-percent", costModel.rdPercent, "", "%");
createInput(costInputs, "Sales & Marketing (%)", "sales-percent", costModel.salesPercent, "", "%");
// Key Metrics Dashboard
const metricsSection = rightPanel.append("div")
.style("background", `linear-gradient(135deg, ${colors.blue}11 0%, ${colors.blue}22 100%)`)
.style("border", `2px solid ${colors.blue}`)
.style("border-radius", "12px")
.style("padding", "12px")
.style("margin-bottom", "12px");
metricsSection.append("div")
.style("font-weight", "bold")
.style("color", colors.blue)
.style("margin-bottom", "10px")
.style("font-size", "13px")
.text("📈 Key Metrics");
const metricsContent = metricsSection.append("div")
.attr("id", "metrics-content");
// Revenue Projection
const projectionSection = rightPanel.append("div")
.style("background", `linear-gradient(135deg, ${colors.green}11 0%, ${colors.green}22 100%)`)
.style("border", `2px solid ${colors.green}`)
.style("border-radius", "12px")
.style("padding", "12px")
.style("margin-bottom", "12px");
projectionSection.append("div")
.style("font-weight", "bold")
.style("color", colors.green)
.style("margin-bottom", "10px")
.style("font-size", "13px")
.text("📊 3-Year Projection");
const projectionContent = projectionSection.append("div")
.attr("id", "projection-content");
// Export/Action buttons
const actionSection = rightPanel.append("div")
.style("display", "flex")
.style("flex-direction", "column")
.style("gap", "8px");
actionSection.append("button")
.style("padding", "10px")
.style("font-size", "12px")
.style("font-weight", "bold")
.style("background", colors.navy)
.style("color", colors.white)
.style("border", "none")
.style("border-radius", "8px")
.style("cursor", "pointer")
.text("📥 Export Canvas Summary")
.on("click", exportCanvas)
.on("mouseenter", function() { d3.select(this).style("background", colors.teal); })
.on("mouseleave", function() { d3.select(this).style("background", colors.navy); });
actionSection.append("button")
.style("padding", "10px")
.style("font-size", "12px")
.style("font-weight", "bold")
.style("background", colors.purple)
.style("color", colors.white)
.style("border", "none")
.style("border-radius", "8px")
.style("cursor", "pointer")
.text("💾 Save for Comparison")
.on("click", saveForComparison)
.on("mouseenter", function() { d3.select(this).style("background", colors.blue); })
.on("mouseleave", function() { d3.select(this).style("background", colors.purple); });
actionSection.append("button")
.style("padding", "10px")
.style("font-size", "12px")
.style("font-weight", "bold")
.style("background", colors.red)
.style("color", colors.white)
.style("border", "none")
.style("border-radius", "8px")
.style("cursor", "pointer")
.text("🗑️ Clear All")
.on("click", () => loadTemplate("blank"))
.on("mouseenter", function() { d3.select(this).style("background", "#c0392b"); })
.on("mouseleave", function() { d3.select(this).style("background", colors.red); });
// Saved models comparison area
const comparisonArea = container.append("div")
.attr("id", "comparison-area")
.style("display", "none")
.style("margin-top", "15px")
.style("background", colors.white)
.style("border", `2px solid ${colors.purple}`)
.style("border-radius", "12px")
.style("padding", "15px");
// Editor modal overlay
const overlay = container.append("div")
.attr("id", "editor-overlay")
.style("display", "none")
.style("position", "fixed")
.style("top", "0")
.style("left", "0")
.style("right", "0")
.style("bottom", "0")
.style("background", "rgba(0,0,0,0.5)")
.style("z-index", "1000")
.on("click", function(event) {
if (event.target === this) closeEditor();
});
// Editor modal
const editorModal = overlay.append("div")
.attr("id", "editor-modal")
.style("position", "absolute")
.style("top", "50%")
.style("left", "50%")
.style("transform", "translate(-50%, -50%)")
.style("background", colors.white)
.style("border-radius", "12px")
.style("padding", "20px")
.style("width", "420px")
.style("max-height", "80vh")
.style("overflow-y", "auto")
.style("box-shadow", "0 20px 60px rgba(0,0,0,0.3)");
// Editor header
const editorHeader = editorModal.append("div")
.style("display", "flex")
.style("justify-content", "space-between")
.style("align-items", "center")
.style("margin-bottom", "15px");
const editorTitle = editorHeader.append("div")
.attr("id", "editor-title")
.style("font-weight", "bold")
.style("font-size", "16px");
editorHeader.append("button")
.style("background", "none")
.style("border", "none")
.style("font-size", "20px")
.style("cursor", "pointer")
.style("color", colors.gray)
.text("×")
.on("click", closeEditor);
// Custom input
const customInputDiv = editorModal.append("div")
.style("display", "flex")
.style("gap", "8px")
.style("margin-bottom", "15px");
customInputDiv.append("input")
.attr("id", "custom-input")
.attr("type", "text")
.attr("placeholder", "Add custom item...")
.style("flex", "1")
.style("padding", "10px")
.style("border", `2px solid ${colors.lightGray}`)
.style("border-radius", "6px")
.style("font-size", "13px")
.on("keypress", function(event) {
if (event.key === "Enter" && this.value.trim()) {
addCustomItem(this.value.trim());
this.value = "";
}
});
customInputDiv.append("button")
.attr("id", "add-custom-btn")
.style("padding", "10px 15px")
.style("background", colors.teal)
.style("color", colors.white)
.style("border", "none")
.style("border-radius", "6px")
.style("cursor", "pointer")
.style("font-weight", "bold")
.text("Add")
.on("click", function() {
const input = container.select("#custom-input").node();
if (input.value.trim()) {
addCustomItem(input.value.trim());
input.value = "";
}
});
// Suggestions section
editorModal.append("div")
.style("font-size", "11px")
.style("color", colors.gray)
.style("margin-bottom", "8px")
.style("text-transform", "uppercase")
.text("Suggestions (click to add)");
editorModal.append("div")
.attr("id", "suggestions-div")
.style("display", "flex")
.style("flex-wrap", "wrap")
.style("gap", "6px")
.style("margin-bottom", "15px");
// Current items section
editorModal.append("div")
.style("font-size", "11px")
.style("color", colors.gray)
.style("margin-bottom", "8px")
.style("text-transform", "uppercase")
.text("Current Items (click to remove)");
editorModal.append("div")
.attr("id", "current-items-div");
// Export output area
const exportArea = container.append("textarea")
.attr("id", "export-area")
.style("display", "none")
.style("width", "100%")
.style("height", "300px")
.style("margin-top", "15px")
.style("padding", "15px")
.style("font-family", "monospace")
.style("font-size", "10px")
.style("border", `2px solid ${colors.lightGray}`)
.style("border-radius", "8px")
.style("resize", "vertical");
// ============================================
// Functions
// ============================================
function loadTemplate(templateKey) {
currentTemplate = templateKey;
const template = templates[templateKey];
canvasData = {
valueProposition: [...(template.data.valueProposition || [])],
customerSegments: [...(template.data.customerSegments || [])],
channels: [...(template.data.channels || [])],
customerRelationships: [...(template.data.customerRelationships || [])],
revenueStreams: [...(template.data.revenueStreams || [])],
keyResources: [...(template.data.keyResources || [])],
keyActivities: [...(template.data.keyActivities || [])],
keyPartners: [...(template.data.keyPartners || [])],
costStructure: [...(template.data.costStructure || [])]
};
revenueModel = { ...template.revenueModel };
costModel = { ...template.costModel };
// Update input values
container.select("#hardware-price").property("value", revenueModel.hardwarePrice);
container.select("#monthly-sub").property("value", revenueModel.monthlySubscription);
container.select("#usage-fee").property("value", revenueModel.usageFee);
container.select("#data-revenue").property("value", revenueModel.dataRevenue);
container.select("#customers-y1").property("value", revenueModel.customersYear1);
container.select("#growth-rate").property("value", revenueModel.growthRate);
container.select("#churn-rate").property("value", revenueModel.churnRate);
container.select("#hardware-cogs").property("value", costModel.hardwareCOGS);
container.select("#cloud-cost").property("value", costModel.cloudCostPerUser);
container.select("#support-cost").property("value", costModel.supportCostPerUser);
container.select("#cac-cost").property("value", costModel.cacCost);
container.select("#rd-percent").property("value", costModel.rdPercent);
container.select("#sales-percent").property("value", costModel.salesPercent);
// Update template button styling
Object.keys(templates).forEach(key => {
container.select(`#template-${key}`)
.style("background", key === templateKey ? colors.teal : "rgba(255,255,255,0.2)")
.style("border-color", key === templateKey ? colors.teal : "rgba(255,255,255,0.3)");
});
updateAllBlocks();
updateMetrics();
container.select("#export-area").style("display", "none");
}
function updateAllBlocks() {
Object.keys(blocks).forEach(key => {
updateBlockDisplay(key);
});
}
function updateBlockDisplay(key) {
const itemsDiv = container.select(`#items-${key}`);
itemsDiv.html("");
const items = canvasData[key];
container.select(`#count-${key}`).text(items.length);
if (items.length === 0) {
itemsDiv.append("div")
.style("color", colors.lightGray)
.style("font-style", "italic")
.text("Click to add...");
} else {
items.forEach(item => {
itemsDiv.append("div")
.style("padding", "2px 0")
.style("border-bottom", `1px solid ${colors.lightGray}`)
.style("color", colors.navy)
.style("white-space", "nowrap")
.style("overflow", "hidden")
.style("text-overflow", "ellipsis")
.text(`• ${item}`);
});
}
}
function openBlockEditor(key) {
activeBlock = key;
const block = blocks[key];
container.select("#editor-overlay").style("display", "block");
container.select("#editor-title")
.style("color", block.color)
.text(`${block.icon} ${block.name}`);
container.select("#add-custom-btn")
.style("background", block.color);
// Update suggestions
const sugDiv = container.select("#suggestions-div");
sugDiv.html("");
block.suggestions.forEach(suggestion => {
const isAdded = canvasData[key].includes(suggestion);
sugDiv.append("button")
.style("padding", "6px 10px")
.style("font-size", "11px")
.style("background", isAdded ? `${block.color}33` : colors.lightGray)
.style("color", isAdded ? block.color : colors.navy)
.style("border", `1px solid ${isAdded ? block.color : colors.lightGray}`)
.style("border-radius", "15px")
.style("cursor", isAdded ? "default" : "pointer")
.style("opacity", isAdded ? "0.6" : "1")
.text(isAdded ? `✓ ${suggestion}` : suggestion)
.on("click", function() {
if (!isAdded) {
canvasData[key].push(suggestion);
updateBlockDisplay(key);
openBlockEditor(key);
}
});
});
updateCurrentItems(key);
}
function updateCurrentItems(key) {
const block = blocks[key];
const currentDiv = container.select("#current-items-div");
currentDiv.html("");
if (canvasData[key].length === 0) {
currentDiv.append("div")
.style("color", colors.lightGray)
.style("font-style", "italic")
.style("font-size", "12px")
.text("No items added yet");
} else {
canvasData[key].forEach((item, index) => {
const itemRow = currentDiv.append("div")
.style("display", "flex")
.style("justify-content", "space-between")
.style("align-items", "center")
.style("padding", "8px")
.style("background", colors.lightGray)
.style("border-radius", "6px")
.style("margin-bottom", "6px");
itemRow.append("span")
.style("font-size", "12px")
.style("color", colors.navy)
.text(item);
itemRow.append("button")
.style("background", colors.red)
.style("color", colors.white)
.style("border", "none")
.style("border-radius", "4px")
.style("padding", "4px 8px")
.style("font-size", "10px")
.style("cursor", "pointer")
.text("✕")
.on("click", function() {
canvasData[key].splice(index, 1);
updateBlockDisplay(key);
openBlockEditor(key);
updateMetrics();
});
});
}
}
function addCustomItem(item) {
if (activeBlock && !canvasData[activeBlock].includes(item)) {
canvasData[activeBlock].push(item);
updateBlockDisplay(activeBlock);
updateCurrentItems(activeBlock);
updateMetrics();
}
}
function closeEditor() {
container.select("#editor-overlay").style("display", "none");
activeBlock = null;
}
function updateMetrics() {
// Read input values
revenueModel.hardwarePrice = parseFloat(container.select("#hardware-price").property("value")) || 0;
revenueModel.monthlySubscription = parseFloat(container.select("#monthly-sub").property("value")) || 0;
revenueModel.usageFee = parseFloat(container.select("#usage-fee").property("value")) || 0;
revenueModel.dataRevenue = parseFloat(container.select("#data-revenue").property("value")) || 0;
revenueModel.customersYear1 = parseInt(container.select("#customers-y1").property("value")) || 0;
revenueModel.growthRate = parseFloat(container.select("#growth-rate").property("value")) || 0;
revenueModel.churnRate = parseFloat(container.select("#churn-rate").property("value")) || 0;
costModel.hardwareCOGS = parseFloat(container.select("#hardware-cogs").property("value")) || 0;
costModel.cloudCostPerUser = parseFloat(container.select("#cloud-cost").property("value")) || 0;
costModel.supportCostPerUser = parseFloat(container.select("#support-cost").property("value")) || 0;
costModel.cacCost = parseFloat(container.select("#cac-cost").property("value")) || 0;
costModel.rdPercent = parseFloat(container.select("#rd-percent").property("value")) || 0;
costModel.salesPercent = parseFloat(container.select("#sales-percent").property("value")) || 0;
// Calculate projections
const projections = [];
let cumulativeCustomers = 0;
for (let year = 1; year <= 3; year++) {
const newCustomers = Math.round(revenueModel.customersYear1 * Math.pow(1 + revenueModel.growthRate / 100, year - 1));
const churnedCustomers = Math.round(cumulativeCustomers * revenueModel.churnRate / 100);
cumulativeCustomers = cumulativeCustomers + newCustomers - churnedCustomers;
const hardwareRevenue = newCustomers * revenueModel.hardwarePrice;
const subscriptionRevenue = cumulativeCustomers * revenueModel.monthlySubscription * 12;
const usageRevenue = cumulativeCustomers * revenueModel.usageFee * 1000 * 12; // Assume 1000 tx/user/mo
const dataRevenueAmt = cumulativeCustomers * revenueModel.dataRevenue * 12;
const totalRevenue = hardwareRevenue + subscriptionRevenue + usageRevenue + dataRevenueAmt;
const hardwareCost = newCustomers * revenueModel.hardwarePrice * (costModel.hardwareCOGS / 100);
const cloudCost = cumulativeCustomers * costModel.cloudCostPerUser * 12;
const supportCost = cumulativeCustomers * costModel.supportCostPerUser * 12;
const cacTotal = newCustomers * costModel.cacCost;
const rdCost = totalRevenue * (costModel.rdPercent / 100);
const salesCost = totalRevenue * (costModel.salesPercent / 100);
const totalCost = hardwareCost + cloudCost + supportCost + cacTotal + rdCost + salesCost;
const grossProfit = totalRevenue - totalCost;
const grossMargin = totalRevenue > 0 ? (grossProfit / totalRevenue) * 100 : 0;
projections.push({
year,
customers: cumulativeCustomers,
newCustomers,
churnedCustomers,
revenue: totalRevenue,
cost: totalCost,
profit: grossProfit,
margin: grossMargin
});
}
// Calculate key metrics
const arpu = revenueModel.monthlySubscription + (revenueModel.usageFee * 1000) + revenueModel.dataRevenue;
const ltv = revenueModel.churnRate > 0 ? arpu * 12 * (100 / revenueModel.churnRate) : arpu * 12 * 10;
const cac = costModel.cacCost;
const ltvCacRatio = cac > 0 ? ltv / cac : 0;
const paybackMonths = arpu > 0 ? cac / arpu : 0;
// Update metrics display
const metricsDiv = container.select("#metrics-content");
metricsDiv.html("");
const metrics = [
{ name: "ARPU (Monthly)", value: `$${arpu.toFixed(2)}`, color: colors.blue },
{ name: "Customer LTV", value: `$${ltv.toLocaleString(undefined, {maximumFractionDigits: 0})}`, color: colors.green },
{ name: "CAC", value: `$${cac.toLocaleString()}`, color: colors.red },
{ name: "LTV:CAC Ratio", value: `${ltvCacRatio.toFixed(1)}x`, color: ltvCacRatio >= 3 ? colors.green : ltvCacRatio >= 1 ? colors.orange : colors.red },
{ name: "Payback Period", value: `${paybackMonths.toFixed(1)} mo`, color: paybackMonths <= 12 ? colors.green : paybackMonths <= 24 ? colors.orange : colors.red },
{ name: "Annual Churn", value: `${revenueModel.churnRate}%`, color: revenueModel.churnRate <= 5 ? colors.green : revenueModel.churnRate <= 15 ? colors.orange : colors.red }
];
metrics.forEach(metric => {
const row = metricsDiv.append("div")
.style("display", "flex")
.style("justify-content", "space-between")
.style("padding", "4px 0")
.style("border-bottom", `1px solid ${colors.lightGray}`)
.style("font-size", "11px");
row.append("span")
.style("color", colors.navy)
.text(metric.name);
row.append("span")
.style("font-weight", "bold")
.style("color", metric.color)
.text(metric.value);
});
// Health indicator
const healthScore = (ltvCacRatio >= 3 ? 1 : 0) + (paybackMonths <= 18 ? 1 : 0) + (revenueModel.churnRate <= 10 ? 1 : 0);
const healthText = healthScore === 3 ? "Healthy" : healthScore === 2 ? "Moderate" : "Needs Work";
const healthColor = healthScore === 3 ? colors.green : healthScore === 2 ? colors.orange : colors.red;
metricsDiv.append("div")
.style("display", "flex")
.style("justify-content", "space-between")
.style("padding", "8px 0")
.style("margin-top", "8px")
.style("border-top", `2px solid ${colors.blue}`)
.style("font-weight", "bold")
.html(`<span style="color:${colors.navy}">Unit Economics</span>
<span style="color:${healthColor}">${healthText}</span>`);
// Update projection display
const projDiv = container.select("#projection-content");
projDiv.html("");
projections.forEach(proj => {
const row = projDiv.append("div")
.style("display", "grid")
.style("grid-template-columns", "50px 1fr 1fr")
.style("gap", "8px")
.style("padding", "6px")
.style("background", colors.lightGray)
.style("border-radius", "6px")
.style("margin-bottom", "6px")
.style("font-size", "10px");
row.append("span")
.style("font-weight", "bold")
.style("color", colors.navy)
.text(`Year ${proj.year}`);
row.append("div")
.html(`<div style="color:${colors.gray}">${proj.customers.toLocaleString()} users</div>
<div style="color:${colors.green}">$${(proj.revenue / 1000000).toFixed(2)}M rev</div>`);
row.append("div")
.html(`<div style="color:${colors.red}">$${(proj.cost / 1000000).toFixed(2)}M cost</div>
<div style="color:${proj.margin >= 0 ? colors.green : colors.red}">${proj.margin.toFixed(0)}% margin</div>`);
});
// 3-year totals
const total3YearRevenue = projections.reduce((sum, p) => sum + p.revenue, 0);
const total3YearProfit = projections.reduce((sum, p) => sum + p.profit, 0);
projDiv.append("div")
.style("display", "flex")
.style("justify-content", "space-between")
.style("padding", "8px")
.style("margin-top", "8px")
.style("border-top", `2px solid ${colors.green}`)
.style("font-weight", "bold")
.style("font-size", "11px")
.html(`<span style="color:${colors.navy}">3-Year Total</span>
<span style="color:${colors.green}">$${(total3YearRevenue / 1000000).toFixed(1)}M revenue</span>`);
}
function saveForComparison() {
const modelName = templates[currentTemplate].name + " - " + new Date().toLocaleTimeString();
savedModels.push({
name: modelName,
template: currentTemplate,
data: { ...canvasData },
revenue: { ...revenueModel },
cost: { ...costModel },
timestamp: new Date()
});
updateComparisonArea();
}
function updateComparisonArea() {
const compArea = container.select("#comparison-area");
if (savedModels.length === 0) {
compArea.style("display", "none");
return;
}
compArea.style("display", "block").html("");
compArea.append("div")
.style("font-weight", "bold")
.style("color", colors.purple)
.style("margin-bottom", "10px")
.style("font-size", "14px")
.text("📊 Model Comparison");
const table = compArea.append("table")
.style("width", "100%")
.style("border-collapse", "collapse")
.style("font-size", "11px");
// Header
const headerRow = table.append("tr");
["Model", "Type", "Y1 Customers", "Y3 Revenue", "LTV:CAC", "Actions"].forEach(h => {
headerRow.append("th")
.style("padding", "8px")
.style("background", colors.purple)
.style("color", colors.white)
.style("text-align", "left")
.text(h);
});
// Rows
savedModels.forEach((model, index) => {
const arpu = model.revenue.monthlySubscription + (model.revenue.usageFee * 1000) + model.revenue.dataRevenue;
const ltv = model.revenue.churnRate > 0 ? arpu * 12 * (100 / model.revenue.churnRate) : arpu * 120;
const ltvCac = model.cost.cacCost > 0 ? ltv / model.cost.cacCost : 0;
// Simple Y3 estimate
const y3Customers = model.revenue.customersYear1 * Math.pow(1 + model.revenue.growthRate / 100, 2);
const y3Revenue = y3Customers * arpu * 12;
const row = table.append("tr")
.style("border-bottom", `1px solid ${colors.lightGray}`);
row.append("td")
.style("padding", "8px")
.style("color", colors.navy)
.text(model.name);
row.append("td")
.style("padding", "8px")
.style("color", colors.gray)
.text(templates[model.template].icon);
row.append("td")
.style("padding", "8px")
.text(model.revenue.customersYear1.toLocaleString());
row.append("td")
.style("padding", "8px")
.style("color", colors.green)
.text(`$${(y3Revenue / 1000000).toFixed(2)}M`);
row.append("td")
.style("padding", "8px")
.style("color", ltvCac >= 3 ? colors.green : colors.orange)
.text(`${ltvCac.toFixed(1)}x`);
const actionsCell = row.append("td")
.style("padding", "8px");
actionsCell.append("button")
.style("padding", "4px 8px")
.style("font-size", "10px")
.style("background", colors.red)
.style("color", colors.white)
.style("border", "none")
.style("border-radius", "4px")
.style("cursor", "pointer")
.text("✕")
.on("click", () => {
savedModels.splice(index, 1);
updateComparisonArea();
});
});
}
function exportCanvas() {
const lines = [
"================================================================",
" IoT BUSINESS MODEL CANVAS",
" Template: " + templates[currentTemplate].name,
" Generated: " + new Date().toLocaleString(),
"================================================================",
""
];
Object.entries(blocks).forEach(([key, block]) => {
lines.push(`--- ${block.icon} ${block.name.toUpperCase()} ---`);
if (canvasData[key].length === 0) {
lines.push(" (No items defined)");
} else {
canvasData[key].forEach(item => {
lines.push(` • ${item}`);
});
}
lines.push("");
});
lines.push("================================================================");
lines.push(" REVENUE MODEL");
lines.push("================================================================");
lines.push(` Hardware Price: $${revenueModel.hardwarePrice}`);
lines.push(` Monthly Subscription: $${revenueModel.monthlySubscription}/mo`);
lines.push(` Usage Fee: $${revenueModel.usageFee}/transaction`);
lines.push(` Data Revenue: $${revenueModel.dataRevenue}/user/mo`);
lines.push(` Year 1 Customers: ${revenueModel.customersYear1}`);
lines.push(` Annual Growth Rate: ${revenueModel.growthRate}%`);
lines.push(` Annual Churn Rate: ${revenueModel.churnRate}%`);
lines.push("");
lines.push("================================================================");
lines.push(" COST STRUCTURE");
lines.push("================================================================");
lines.push(` Hardware COGS: ${costModel.hardwareCOGS}%`);
lines.push(` Cloud Cost/User: $${costModel.cloudCostPerUser}/mo`);
lines.push(` Support Cost/User: $${costModel.supportCostPerUser}/mo`);
lines.push(` Customer Acquisition Cost: $${costModel.cacCost}`);
lines.push(` R&D (% of revenue): ${costModel.rdPercent}%`);
lines.push(` Sales & Marketing: ${costModel.salesPercent}%`);
lines.push("");
// Calculate key metrics for export
const arpu = revenueModel.monthlySubscription + (revenueModel.usageFee * 1000) + revenueModel.dataRevenue;
const ltv = revenueModel.churnRate > 0 ? arpu * 12 * (100 / revenueModel.churnRate) : arpu * 120;
const ltvCac = costModel.cacCost > 0 ? ltv / costModel.cacCost : 0;
lines.push("================================================================");
lines.push(" KEY METRICS");
lines.push("================================================================");
lines.push(` ARPU (Monthly): $${arpu.toFixed(2)}`);
lines.push(` Customer LTV: $${ltv.toLocaleString(undefined, {maximumFractionDigits: 0})}`);
lines.push(` CAC: $${costModel.cacCost}`);
lines.push(` LTV:CAC Ratio: ${ltvCac.toFixed(1)}x`);
lines.push("");
lines.push("================================================================");
const exportAreaEl = container.select("#export-area");
exportAreaEl.style("display", "block")
.property("value", lines.join("\n"));
exportAreaEl.node().select();
}
// Initialize
loadTemplate("productAsService");
return container.node();
}139.2 Understanding IoT Business Models
139.2.1 The Four Core IoT Business Model Patterns
IoT businesses typically follow one of four primary revenue models, each with distinct characteristics:
| Model | Description | Revenue Sources | Best For |
|---|---|---|---|
| Product-as-a-Service | Hardware + recurring software | Device sales, subscriptions | B2B industrial |
| Data Monetization | Value from aggregated data | Data licensing, insights | Consumer IoT |
| Platform/Ecosystem | Multi-sided marketplace | Transactions, subscriptions | Smart home, wearables |
| Outcome-Based | Pay for results | Performance fees | Energy, industrial |
139.2.2 Key Metrics for IoT Businesses
| Metric | Target | Why It Matters |
|---|---|---|
| LTV:CAC Ratio | > 3:1 | Sustainable customer acquisition |
| Payback Period | < 18 months | Cash flow efficiency |
| Annual Churn | < 10% | Customer retention |
| ARPU Growth | Positive | Expanding customer value |
| Gross Margin | > 60% | Scalability potential |
139.2.3 Revenue Model Considerations
Hardware + Subscription (Product-as-a-Service) - Pros: Predictable recurring revenue, customer lock-in - Cons: Hardware complexity, inventory risk - Best for: Industrial IoT, B2B applications
Data Monetization - Pros: High margins, scales well - Cons: Privacy concerns, data quality dependencies - Best for: Consumer IoT, large device networks
Platform/Ecosystem - Pros: Network effects, multiple revenue streams - Cons: Chicken-and-egg problem, high development costs - Best for: Smart home, connected devices
Outcome-Based - Pros: Aligned incentives, premium pricing - Cons: Performance risk, complex contracts - Best for: Energy efficiency, predictive maintenance
139.2.4 Cost Structure Breakdown
| Category | Typical % of Revenue | Key Drivers |
|---|---|---|
| Hardware COGS | 30-60% | Bill of materials, manufacturing |
| Cloud Infrastructure | 5-15% | Data storage, compute, bandwidth |
| R&D | 15-30% | Software development, hardware iteration |
| Sales & Marketing | 20-35% | Customer acquisition, brand building |
| Customer Support | 5-10% | Technical support, success management |
139.2.5 Building a Sustainable IoT Business
- Start with unit economics: Ensure positive LTV:CAC before scaling
- Focus on customer success: Low churn is critical for subscription models
- Plan for hardware lifecycle: Devices need updates, replacements, and end-of-life planning
- Build data moats: Proprietary data becomes increasingly valuable over time
- Consider total cost of ownership: Include connectivity, maintenance, and support costs
139.3 Related Topics
- IoT Application Domains - Industry-specific IoT applications
- IoT Use Case Builder - Design custom IoT use cases
- Industry 4.0 Maturity Assessment - Assess manufacturing digital maturity
Interactive tool created for the IoT Class Textbook