Ballpark Book LOE
by Daniel Lamb
HTML
<script src="//cdnjs.cloudflare.com/ajax/libs/moment.js/2.8.3/moment.min.js"></script>
<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.3.2/angular.min.js"></script>
<div ng-app="Author">
<form ng-controller="LOE">
<label>Start date</label>
<input type="date" ng-model="start" />
<label>Number of pages to write</label>
<input type="number" min="1" max="500" ng-model="pages" />
<label>Number of hours to write one page</label>
<input type="number" min="1" max="72" ng-model="hoursPerPage" />
<label>Number of hours in a work day you will write</label>
<input type="number" min="1" max="24" ng-model="hoursPerDay" />
<label>Hourly consultant rate $</label>
<input type="number" min="20" max="900" ng-model="consultantRate" />
<label>Estimated retail book price $</label>
<input type="number" min="1" max="100" ng-model="retailPrice" />
<label>O'Reilly royalty percentage </label>
<input type="number" min="1" max="20" ng-model="royalty" />
<label class="heading">
Your {{chapterCount | number}} chapter book should be done {{relative}} on {{date}}
</label>
<label class="subheading">Cost</label>
<label>
You could have made {{cost | currency}} consulting for {{hours | number:0}} hours.
</label>
<label>
With O'Reilly you'll make {{royalties | currency}} on each book, and would have to sell {{breakEven | number:0}} books to make that much.
</label>
<label>
With LeanPub you'll make {{lpRoyalties | currency}} on each book, and only have to sell {{lpBreakEven | number:0}} books to make that much.
</label>
<label class="subheading">Deadlines</label>
<ul ng-repeat="chapter in chapters">
<li>Chapter {{$index + 1}} due {{chapter.relative}} on {{chapter.date}}</li>
</ul>
</form>
</div>
CSS
label, input, ul {
float: left;
}
label {
clear: left;
padding-right: 10px;
}
ul {
clear: left;
margin: 0;
padding: 0 0 5px 10px;
list-style-type: none;
}
.heading {
margin-top: 20px;
font-size: 25px;
}
.subheading {
margin: 20px 0 5px 0;
font-size: 20px;
}
JavaScript
angular.module('Author', []).controller('LOE', ['$scope', function ($scope) {
'use strict';
var PAGES_PER_CHAPTER = 25,
WHOLESALE_DISCOUNT = 0.52;
// assume starting today
$scope.start = new Date();
// number of pages to write in the book
$scope.pages = 250;
// number of hours to write one page
$scope.hoursPerPage = 2;
// number of hours in a work day you will write
$scope.hoursPerDay = 3;
// consultant hourly rate
$scope.consultantRate = 80;
// estimated full retail price
$scope.retailPrice = 24.93;
// estimated royalty (typically between 10 and 15 percent)
$scope.royalty = 12;
// calculate the number of days based on the work load settings
function calcDays(pages) {
return pages * $scope.hoursPerPage / $scope.hoursPerDay;
}
// add weekends to the total number of days
function excludeWeekends(days) {
return days += (days / 5 * 2);
}
// convert days to relative time and estimated date
function convertDays(days) {
return [
moment($scope.start).add(days, 'days').fromNow(),
moment($scope.start).add(days, 'days').format('MMMM Do YYYY')
];
}
// calculate royalty income
function calcNetRoyalty() {
// estimate wholesale price as 52% off retail
var wholesalePrice = $scope.retailPrice - ($scope.retailPrice * WHOLESALE_DISCOUNT);
return wholesalePrice * ($scope.royalty / 100) ;
}
// watch for model changes
$scope.$watchGroup([
'start', 'pages', 'hoursPerPage', 'hoursPerDay',
'consultantRate', 'retailPrice', 'royalty'
], function () {
// calculate the number of days
var days = calcDays($scope.pages);
// calculate hourly cost
$scope.hours = (days * $scope.hoursPerDay);
$scope.cost = $scope.hours * $scope.consultantRate;
// calculate O'Reilly royalty income
$scope.royalties =...