Loan Calculator

Table of Contents

Fork me on GitHub

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

Loan Amount
Interest Rate
Years
Payments per year
Payment Amount

Principle / Interest Graph

The interest payment (top) is blue and the principle payment (bottom) in yellow.

Schedule

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();
    });

})();

Date: <2014-09-26 Fri>

Author: Peter Moresi

Created: 2015-03-10 Tue 23:48

Emacs 24.4.1 (Org mode 8.2.10)

Validate