Skip to content

Instantly share code, notes, and snippets.

@indexzero
Last active December 20, 2015 00:39
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save indexzero/6043821 to your computer and use it in GitHub Desktop.
Save indexzero/6043821 to your computer and use it in GitHub Desktop.
Sizing up the PaaS Industry
//
// 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]);
});
});
});
<!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>
/********************
* 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);
}
//
// 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