Loan Calculator
Table of Contents
About
This is a programming example to demonstrate literate programming with org-mode.
The target language is JavaScript and if you have JavaScript enabled then the extracted program is running inside the document that you are reading now.
The documentation and programming are contained inside a single org file.
The website is generated by org-mode with layout provided by ReadTheOrg.
User Input
Principle / Interest Graph
The interest payment (top) is blue and the principle payment (bottom) in yellow.
Source Code code
The source code is written JavaScript; and depends on amortize.js.
This function reads the values from the HTML inputs and returns a JSObject with:
The application includes the following inputs:
- loan_amount
- interest_rate
- payments_per_year
- years
Functions
- Get DataSet
Reads the inputs, runs calculations and returns the dataset.
function getDataSet() { var output = {}; var loanAmount = output.loanAmount = parseFloat( $('#loanAmount').val() ); var interestRate = output.interestRate = parseFloat( $('#interestRate').val() ); var paymentsPerYear = output.paymentsPerYear = parseInt( $('#paymentsPerYear').val() ); var years = output.years = parseInt( $('#years').val() ); var numberOfPayments = output.numberOfPayments = paymentsPerYear * years; var payment = output.payment = pmt(interestRate/100/paymentsPerYear, numberOfPayments, -loanAmount); output.schedule = computeSchedule( loanAmount, interestRate, paymentsPerYear, years, payment ); return output; }
- Reload Table
The 'reloadTable' function clears the schedule and reloads using datatables.net.
function reloadTable(ds) { // map the schedule to 2 digits after decimal point. var schedule = ds.schedule.map( function(n) { return [n[0], n[1].toFixed(2), n[2].toFixed(2), n[3].toFixed(2)]; }); $('#schedule').empty(); $('#schedule').html( '<table cellpadding="0" cellspacing="0" border="0" class="display table" id="schedule_table"></table>' ); $('#schedule_table').dataTable( { "data": schedule, "searching": false, "columns": [ { "title": "Period" }, { "title": "Principle" }, { "title": "Interest" }, { "title": "Remaining" } ], "search": false, "paging": false, "ordering": false, "info": false } ); }
- Reload Graph
The 'reloadGraph' clears the graph and reloads
function reloadGraph(ds) { var graphWidth = $('#outline-container-sec-2').width(); // make graph same width as table var graphHeight = $('#graph').height(); var periodWidth = Math.round(graphWidth / (ds.numberOfPayments)); // adjust graphy width for rounding of period width graphWidth = periodWidth * ds.numberOfPayments; $('#graph').empty(); $('#graph').width(graphWidth); for (var count = 0; count < ds.numberOfPayments; count++) { var i = ds.schedule[count][1]; var p = ds.schedule[count][2]; var t = i + p; var ratio = i / t; var height = Math.round(graphHeight * ratio); $('<div style="float: left; margin-top: ' + (graphHeight-height).toString() + 'px; background-color: yellow; height: ' + height + 'px; width: ' + periodWidth + 'px"></div>').appendTo('#graph'); } }
- Reload
The 'reload' function reads the dataset and bindt the data to the HTML document.
function reload() { var ds = getDataSet(); $('#paymentAmount').text('$' + ds.payment.toFixed(2)); reloadTable(ds); reloadGraph(ds); } $(document).on('keyup', '.user-input', reload);
- Handle Page Ready
$(document).ready(function() { reload(); });
Complete Script
(function() { function getDataSet() { var output = {}; var loanAmount = output.loanAmount = parseFloat( $('#loanAmount').val() ); var interestRate = output.interestRate = parseFloat( $('#interestRate').val() ); var paymentsPerYear = output.paymentsPerYear = parseInt( $('#paymentsPerYear').val() ); var years = output.years = parseInt( $('#years').val() ); var numberOfPayments = output.numberOfPayments = paymentsPerYear * years; var payment = output.payment = pmt(interestRate/100/paymentsPerYear, numberOfPayments, -loanAmount); output.schedule = computeSchedule( loanAmount, interestRate, paymentsPerYear, years, payment ); return output; } function reloadTable(ds) { // map the schedule to 2 digits after decimal point. var schedule = ds.schedule.map( function(n) { return [n[0], n[1].toFixed(2), n[2].toFixed(2), n[3].toFixed(2)]; }); $('#schedule').empty(); $('#schedule').html( '<table cellpadding="0" cellspacing="0" border="0" class="display table" id="schedule_table"></table>' ); $('#schedule_table').dataTable( { "data": schedule, "searching": false, "columns": [ { "title": "Period" }, { "title": "Principle" }, { "title": "Interest" }, { "title": "Remaining" } ], "search": false, "paging": false, "ordering": false, "info": false } ); } function reloadGraph(ds) { var graphWidth = $('#outline-container-sec-2').width(); // make graph same width as table var graphHeight = $('#graph').height(); var periodWidth = Math.round(graphWidth / (ds.numberOfPayments)); // adjust graphy width for rounding of period width graphWidth = periodWidth * ds.numberOfPayments; $('#graph').empty(); $('#graph').width(graphWidth); for (var count = 0; count < ds.numberOfPayments; count++) { var i = ds.schedule[count][1]; var p = ds.schedule[count][2]; var t = i + p; var ratio = i / t; var height = Math.round(graphHeight * ratio); $('<div style="float: left; margin-top: ' + (graphHeight-height).toString() + 'px; background-color: yellow; height: ' + height + 'px; width: ' + periodWidth + 'px"></div>').appendTo('#graph'); } } function reload() { var ds = getDataSet(); $('#paymentAmount').text('$' + ds.payment.toFixed(2)); reloadTable(ds); reloadGraph(ds); } $(document).on('keyup', '.user-input', reload); $(document).ready(function() { reload(); }); })();