Last active
December 20, 2015 00:39
-
-
Save indexzero/6043821 to your computer and use it in GitHub Desktop.
Sizing up the PaaS Industry
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// All known pricing data. | |
// | |
var prices = { | |
paas: { | |
nodejitsu: { | |
hourly: { | |
'Micro': 0.0125, | |
'Small': 0.015, | |
'Regular': 0.013, | |
'Large': 0.0125 | |
}, | |
monthly: { | |
'Micro': 9.0, | |
'Small': 33.0, | |
'Regular': 50.0, | |
'Large': 90.0 | |
} | |
}, | |
comparison: { | |
hourly: { | |
'AppFog': 0.0033, | |
'CloudFoundry': 0.0075, | |
'Nodejitsu': 0.0125, | |
'Modulus': 0.0129, | |
'OpenShift': 0.02, | |
'Heroku': 0.025, | |
'AppHarbor': 0.034, | |
'dotCloud': 0.048 | |
}, | |
monthly: { | |
'AppFog': 20.0, | |
'CloudFoundry': 21.6, | |
'Nodejitsu': 9.0, | |
'Modulus': 14.40, | |
'OpenShift': 20.0, | |
'Heroku': 36.0, | |
'AppHarbor': 49.0, | |
'dotCloud': 34.56 | |
} | |
}, | |
'comparison-large-app': { | |
hourly: { | |
'Nodejitsu': 0.125, | |
'CloudFoundry': 0.150, | |
'AppFog': 0.1389, | |
'Modulus': (0.02 * 10), | |
'OpenShift': 0.3077, | |
'Heroku': (0.05 * 9).toFixed(4), | |
'dotCloud': (0.048 * 10).toFixed(4), | |
'AppHarbor': 0.6847 | |
}, | |
monthly: { | |
'Nodejitsu': 90.00, | |
'CloudFoundry': 108.00, | |
'AppFog': 100.00, | |
'Modulus': 144.00, | |
'OpenShift': 221.60, | |
'Heroku': 324.00, | |
'dotCloud': 345.60, | |
'AppHarbor': 439.00 | |
} | |
} | |
} | |
}; | |
// | |
// Store all charts for later use | |
// | |
var charts = {}; | |
// | |
// ### function tooltipContent (key, x, y, e, graph) | |
// Formats the tooltip based on the correct (full) y-value. | |
// | |
function tooltipContent(key, x, y, e, graph) { | |
return '<h3>' + key + '</h3>' + | |
'<p>' + e.point.value + ' on ' + x + '</p>'; | |
} | |
// | |
// ### function namedSeries (options) | |
// Generates a named series of options.data that | |
// conforms to the conventions of nvd3.js | |
// | |
function namedSeries(options) { | |
return [{ | |
values: Object.keys(options.data) | |
.map(function (key) { | |
return { | |
value: options.data[key], | |
label: key | |
} | |
}) | |
}]; | |
} | |
// | |
// ### function createPricingSparkChart(options) | |
// Creates a new nvd3 chart (defaulting to `discreteBarChart`) | |
// for the specified `options` as a "spark chart" (i.e. small) | |
// | |
function createPricingSparkChart(options) { | |
options.id += '-spark'; | |
options.staggerLabels = true; | |
options.tooltips = false; | |
options.showValues = false; | |
delete options.xAxis; | |
createPricingChart(options); | |
} | |
// | |
// ### function createPricingChart(options) | |
// Creates a new nvd3 chart (defaulting to `discreteBarChart`) | |
// for the specified `options`. | |
// | |
function createPricingChart(options) { | |
options.type = options.type || 'discreteBarChart'; | |
if (typeof options.tooltips === 'undefined') { options.tooltips = true } | |
if (typeof options.showValues === 'undefined') { options.showValues = true } | |
nv.addGraph(function () { | |
var chart = nv.models[options.type]() | |
.x(function (d) { return d.label }) | |
.y(function (d) { return d.value }); | |
if (options.onChart) { | |
chart = options.onChart(chart, options); | |
} | |
if (options.xAxis && options.xAxis.label) { | |
chart.xAxis | |
.axisLabel(options.xAxis.label); | |
} | |
var svg = d3.select('#' + options.id + ' figure svg'); | |
svg.datum(namedSeries(options)) | |
.transition() | |
.duration(500) | |
.call(chart); | |
nv.utils.windowResize(chart.update); | |
if (!options.staggerLabels) { | |
// | |
// Select xAxis ticks for label styling. | |
// | |
var xTicks = svg.select('g.nv-wrap.nv-discreteBarWithAxes') | |
.select('g') | |
.select('.nv-x.nv-axis > g'); | |
xTicks | |
.selectAll('g > text') | |
.style('font', 'normal 14px "Arvo", "Helvetica Neue", Helvetica, Arial, sans-serif') | |
.attr('transform', 'translate(0,2)'); | |
xTicks.select('g > text.nv-axislabel') | |
.style('font', 'normal 20px "Arvo", "Helvetica Neue", Helvetica, Arial, sans-serif') | |
.attr('transform', 'translate(0,10)') | |
.style('text-decoration', 'underline'); | |
} | |
charts[options.id] = chart; | |
return chart; | |
}); | |
if (!/-spark$/.test(options.id)) { | |
createPricingSparkChart( | |
Object.keys(options).reduce(function (copy, key) { | |
copy[key] = options[key]; | |
return copy; | |
}, {}) | |
); | |
} | |
} | |
// | |
// Create a chart for Nodejitsu Individual Plans | |
// | |
createPricingChart({ | |
name: 'Nodejitsu Individual Plans', | |
id: 'individual-plans', | |
xAxis: { label: 'Plan' }, | |
data: prices.paas.nodejitsu.hourly, | |
onChart: function (chart, options) { | |
chart.yAxis | |
.axisLabel('Price') | |
.tickFormat(d3.format(',.3f')); | |
return chart.staggerLabels(options.staggerLabels || false) | |
.tooltips(options.tooltips) | |
.tooltipContent(tooltipContent) | |
.showValues(options.showValues) | |
.valueFormat(d3.format(',.4f')); | |
} | |
}); | |
// | |
// Create a chart for all Platform-as-a-Service providers. | |
// | |
createPricingChart({ | |
name: 'Pricing Comparison', | |
id: 'pricing-comparison', | |
xAxis: { label: 'PaaS Provider' }, | |
data: prices.paas.comparison.hourly, | |
onChart: function (chart, options) { | |
chart.yAxis | |
.axisLabel('Price') | |
.tickFormat(d3.format(',.3f')); | |
return chart.staggerLabels(options.staggerLabels || false) | |
.tooltips(options.tooltips) | |
.tooltipContent(tooltipContent) | |
.showValues(options.showValues) | |
.valueFormat(d3.format(',.4f')); | |
} | |
}); | |
// | |
// Create a chart for all Platform-as-a-Service providers. | |
// | |
createPricingChart({ | |
name: 'Pricing Comparison (complex hosting)', | |
id: 'pricing-comparison-multiple-apps', | |
xAxis: { label: 'PaaS Provider' }, | |
data: prices.paas['comparison-large-app'].hourly, | |
onChart: function (chart, options) { | |
chart.yAxis | |
.axisLabel('Price') | |
.tickFormat(d3.format(',.3f')); | |
return chart.staggerLabels(options.staggerLabels || false) | |
.tooltips(options.tooltips) | |
.tooltipContent(tooltipContent) | |
.showValues(options.showValues) | |
.valueFormat(d3.format(',.4f')); | |
} | |
}); | |
// | |
// Setup click handlers | |
// | |
$(function () { | |
$('.selection a').each(function (i, el) { | |
var anchor = $(el); | |
anchor.click(function () { | |
if (anchor.hasClass('shown')) { return; } | |
var state = { | |
shown: anchor.hasClass('shown'), | |
chart: anchor.data('chart'), | |
series: anchor.data('series'), | |
time: anchor.data('time') | |
}; | |
state.from = state.time === 'hourly' | |
? 'monthly' | |
: 'hourly'; | |
$('#' + state.chart + ' .selection .shown').first().removeClass('shown'); | |
anchor.addClass('shown'); | |
var svg = d3.select('#' + state.chart + ' figure svg'); | |
svg.datum(namedSeries({ data: prices.paas[state.series][state.time] })) | |
.transition() | |
.duration(500) | |
.call(charts[state.chart]); | |
}); | |
}); | |
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!doctype html> | |
<html lang="en"> | |
<head> | |
<meta charset="utf-8"> | |
<title>Platform-as-a-Service Pricing Comparison</title> | |
<meta name="description" content="Platform-as-a-Service Pricing Comparison"> | |
<meta name="author" content="Charlie Robbins"> | |
<meta name="apple-mobile-web-app-capable" content="yes" /> | |
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" /> | |
<link rel="stylesheet" href="nv.d3.css"> | |
<link href='http://fonts.googleapis.com/css?family=Arvo:400,700,400italic,700italic' rel='stylesheet' type='text/css'> | |
<style> | |
h2 { | |
font-family: "Arvo", "Helvetica Neue", Helvetica, Arial, sans-serif; | |
font-size: 24px; | |
font-weight: 200; | |
letter-spacing: -0.02em; | |
color: black; | |
text-align: center; | |
} | |
p { | |
font-family: sans-serif; | |
font-size: 18px; | |
line-height: 24px; | |
} | |
figure { | |
height: 450px; | |
width: 900px; | |
} | |
.nv-x, #sparks div { | |
font-family: "Arvo", "Helvetica Neue", Helvetica, Arial, sans-serif; | |
font-size: 24px; | |
font-weight: 200; | |
letter-spacing: -0.02em; | |
color: black; | |
text-align: center; | |
} | |
#sparks figure { | |
clear: both; | |
height: 150px; | |
width: 300px; | |
margin: 0; | |
} | |
#sparks div { | |
float: left; | |
font-size: 16px; | |
text-decoration: underline; | |
} | |
.selection a { | |
background-color: #aec7e8; | |
border-radius: 10px; | |
color: #666; | |
cursor: pointer; | |
display: inline-block; | |
font-size: 14px; | |
margin: 0 15px 0 0; | |
padding: 8px 10px; | |
text-decoration: none; | |
} | |
.selection a.shown { | |
background-color: #1f77b4; | |
color: #FFF; | |
} | |
.selection a:first-child { margin-left: 80px; } | |
.narrow a:first-child { margin-left: 45px; } | |
hr { margin: 40px 0 } | |
</style> | |
</head> | |
<body> | |
<div style="width:900px;margin: 0 auto; text-align: left"> | |
<p>When we <a href="http://blog.nodejitsu.com/changes-in-nodejitsu-public-cloud">announced new pricing for our Individual Plans earlier this month</a>, we made a claim that <strong>even with these changes Nodejitsu is still the cheapest Platform-as-a-Service on the market today.</strong> This post aims to support that claim with the only thing that really matters: <em>data</em>.</p> | |
<p>Best viewed at <a href="http://bl.ocks.org/indexzero/6043821">bl.ocks.org/indexzero/6043821</a></p> | |
<section id="sparks"> | |
<div id="pricing-comparison-spark"> | |
PaaS Pricing Comparison | |
<figure><svg></svg></figure> | |
</div> | |
<div id="individual-plans-spark"> | |
Nodejitsu Plans | |
<figure><svg></svg></figure> | |
</div> | |
<div id="pricing-comparison-multiple-apps-spark"> | |
Large App Pricing Comparison | |
<figure><svg></svg></figure> | |
</div> | |
</section> | |
<hr> | |
<div style="clear:both"></div> | |
<section id="pricing-comparison"> | |
<h2> | |
Platform-as-a-Service Pricing Comparison. | |
<span class="selection"> | |
<a data-chart="pricing-comparison" data-series="comparison" data-time="hourly" class="shown">Hourly</a> | |
<a data-chart="pricing-comparison" data-series="comparison" data-time="monthly">Monthly</a> | |
</span> | |
</h2> | |
<figure><svg></svg></figure> | |
</section> | |
<hr> | |
<section id="individual-plans"> | |
<h2> | |
Nodejitsu Individual Plan Pricing Comparison. | |
<span class="selection"> | |
<a data-chart="individual-plans" data-series="nodejitsu" data-time="hourly" class="shown">Hourly</a> | |
<a data-chart="individual-plans" data-series="nodejitsu" data-time="monthly">Monthly</a> | |
</span> | |
</h2> | |
<figure><svg></svg></figure> | |
</section> | |
<hr> | |
<section id="pricing-comparison-multiple-apps"> | |
<h2> | |
Platform-as-a-Service Pricing Comparison (complex app) | |
<span class="selection narrow"> | |
<a data-chart="pricing-comparison-multiple-apps" data-series="comparison-large-app" data-time="hourly" class="shown">Hourly</a> | |
<a data-chart="pricing-comparison-multiple-apps" data-series="comparison-large-app" data-time="monthly">Monthly</a> | |
</span> | |
</h2> | |
<figure><svg></svg></figure> | |
</section> | |
</div> | |
<script src="http://cdnjs.cloudflare.com/ajax/libs/jquery/2.0.3/jquery.min.js"></script> | |
<script src="http://cdnjs.cloudflare.com/ajax/libs/d3/2.10.0/d3.v2.min.js"></script> | |
<script src="http://cdnjs.cloudflare.com/ajax/libs/nvd3/1.0.0-beta/nv.d3.min.js"></script> | |
<script src="comparison.js"></script> | |
</body> | |
</html> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/******************** | |
* HTML CSS | |
*/ | |
.chartWrap { | |
margin: 0; | |
padding: 0; | |
overflow: hidden; | |
} | |
/******************** | |
* TOOLTIP CSS | |
*/ | |
.nvtooltip { | |
position: absolute; | |
background-color: rgba(255,255,255,1); | |
padding: 1px; | |
border: 1px solid rgba(0,0,0,.2); | |
z-index: 10000; | |
font-family: Arial; | |
font-size: 13px; | |
transition: opacity 500ms linear; | |
-moz-transition: opacity 500ms linear; | |
-webkit-transition: opacity 500ms linear; | |
transition-delay: 500ms; | |
-moz-transition-delay: 500ms; | |
-webkit-transition-delay: 500ms; | |
-moz-box-shadow: 0 5px 10px rgba(0,0,0,.2); | |
-webkit-box-shadow: 0 5px 10px rgba(0,0,0,.2); | |
box-shadow: 0 5px 10px rgba(0,0,0,.2); | |
-webkit-border-radius: 6px; | |
-moz-border-radius: 6px; | |
border-radius: 6px; | |
pointer-events: none; | |
-webkit-touch-callout: none; | |
-webkit-user-select: none; | |
-khtml-user-select: none; | |
-moz-user-select: none; | |
-ms-user-select: none; | |
user-select: none; | |
} | |
.nvtooltip.x-nvtooltip, | |
.nvtooltip.y-nvtooltip { | |
padding: 8px; | |
} | |
.nvtooltip h3 { | |
margin: 0; | |
padding: 4px 14px; | |
line-height: 18px; | |
font-weight: normal; | |
background-color: #f7f7f7; | |
text-align: center; | |
border-bottom: 1px solid #ebebeb; | |
-webkit-border-radius: 5px 5px 0 0; | |
-moz-border-radius: 5px 5px 0 0; | |
border-radius: 5px 5px 0 0; | |
} | |
.nvtooltip p { | |
margin: 0; | |
padding: 5px 14px; | |
text-align: center; | |
} | |
.nvtooltip span { | |
display: inline-block; | |
margin: 2px 0; | |
} | |
.nvtooltip-pending-removal { | |
position: absolute; | |
pointer-events: none; | |
} | |
/******************** | |
* SVG CSS | |
*/ | |
svg { | |
-webkit-touch-callout: none; | |
-webkit-user-select: none; | |
-khtml-user-select: none; | |
-moz-user-select: none; | |
-ms-user-select: none; | |
user-select: none; | |
/* Trying to get SVG to act like a greedy block in all browsers */ | |
display: block; | |
width:100%; | |
height:100%; | |
} | |
svg text { | |
font: normal 12px Arial; | |
} | |
svg .title { | |
font: bold 14px Arial; | |
} | |
.nvd3 .nv-background { | |
fill: white; | |
fill-opacity: 0; | |
/* | |
pointer-events: none; | |
*/ | |
} | |
.nvd3.nv-noData { | |
font-size: 18px; | |
font-weight: bold; | |
} | |
/********** | |
* Legend | |
*/ | |
.nvd3 .nv-legend .nv-series { | |
cursor: pointer; | |
} | |
.nvd3 .nv-legend .disabled circle { | |
fill-opacity: 0; | |
} | |
/********** | |
* Axes | |
*/ | |
.nvd3 .nv-axis path { | |
fill: none; | |
stroke: #000; | |
stroke-opacity: .75; | |
shape-rendering: crispEdges; | |
} | |
.nvd3 .nv-axis path.domain { | |
stroke-opacity: .75; | |
} | |
.nvd3 .nv-axis.nv-x path.domain { | |
stroke-opacity: 0; | |
} | |
.nvd3 .nv-axis line { | |
fill: none; | |
stroke: #000; | |
stroke-opacity: .25; | |
shape-rendering: crispEdges; | |
} | |
.nvd3 .nv-axis line.zero { | |
stroke-opacity: .75; | |
} | |
.nvd3 .nv-axis .nv-axisMaxMin text { | |
font-weight: bold; | |
} | |
.nvd3 .x .nv-axis .nv-axisMaxMin text, | |
.nvd3 .x2 .nv-axis .nv-axisMaxMin text, | |
.nvd3 .x3 .nv-axis .nv-axisMaxMin text { | |
text-anchor: middle | |
} | |
/********** | |
* Bars | |
*/ | |
.nvd3 .nv-bars .negative rect { | |
zfill: brown; | |
} | |
.nvd3 .nv-bars rect { | |
zfill: steelblue; | |
fill-opacity: .75; | |
transition: fill-opacity 250ms linear; | |
-moz-transition: fill-opacity 250ms linear; | |
-webkit-transition: fill-opacity 250ms linear; | |
} | |
.nvd3 .nv-bars rect:hover { | |
fill-opacity: 1; | |
} | |
.nvd3 .nv-bars .hover rect { | |
fill: lightblue; | |
} | |
.nvd3 .nv-bars text { | |
fill: rgba(0,0,0,0); | |
} | |
.nvd3 .nv-bars .hover text { | |
fill: rgba(0,0,0,1); | |
} | |
/********** | |
* Bars | |
*/ | |
.nvd3 .nv-multibar .nv-groups rect, | |
.nvd3 .nv-multibarHorizontal .nv-groups rect, | |
.nvd3 .nv-discretebar .nv-groups rect { | |
stroke-opacity: 0; | |
transition: fill-opacity 250ms linear; | |
-moz-transition: fill-opacity 250ms linear; | |
-webkit-transition: fill-opacity 250ms linear; | |
} | |
.nvd3 .nv-multibar .nv-groups rect:hover, | |
.nvd3 .nv-multibarHorizontal .nv-groups rect:hover, | |
.nvd3 .nv-discretebar .nv-groups rect:hover { | |
fill-opacity: 1; | |
} | |
.nvd3 .nv-discretebar .nv-groups text, | |
.nvd3 .nv-multibarHorizontal .nv-groups text { | |
font-weight: bold; | |
fill: rgba(0,0,0,1); | |
stroke: rgba(0,0,0,0); | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// All known pricing data. | |
// | |
var prices = { | |
paas: { | |
nodejitsu: { | |
hourly: { | |
'Micro': 0.0125, | |
'Small': 0.015, | |
'Regular': 0.013, | |
'Large': 0.0125 | |
}, | |
monthly: { | |
'Micro': 9.0, | |
'Small': 33.0, | |
'Regular': 50.0, | |
'Large': 90.0 | |
} | |
}, | |
comparison: { | |
hourly: { | |
'AppFog': 0.0033, | |
'CloudFoundry': 0.0075, | |
'Nodejitsu': 0.0125, | |
'Modulus': 0.0129, | |
'OpenShift': 0.02, | |
'Heroku': 0.025, | |
'AppHarbor': 0.034, | |
'dotCloud': 0.048 | |
}, | |
monthly: { | |
'AppFog': 20.0, | |
'CloudFoundry': 21.6, | |
'Nodejitsu': 9.0, | |
'Modulus': 14.40, | |
'OpenShift': 20.0, | |
'Heroku': 36.0, | |
'AppHarbor': 49.0, | |
'dotCloud': 34.56 | |
} | |
}, | |
'comparison-large-app': { | |
hourly: { | |
'Nodejitsu': 0.125, | |
'CloudFoundry': 0.150, | |
'AppFog': 0.1389, | |
'Modulus': (0.02 * 10), | |
'OpenShift': 0.3077, | |
'Heroku': (0.05 * 9).toFixed(4), | |
'dotCloud': (0.048 * 10).toFixed(4), | |
'AppHarbor': 0.6847 | |
}, | |
monthly: { | |
'Nodejitsu': 90.00, | |
'CloudFoundry': 108.00, | |
'AppFog': 100.00, | |
'Modulus': 144.00, | |
'OpenShift': 221.60, | |
'Heroku': 324.00, | |
'dotCloud': 345.60, | |
'AppHarbor': 439.00 | |
} | |
} | |
} | |
}; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment