Hosted by |
|
Mortgage Payments: Interest and Principal
This Web page shows a simple example that uses the
Tcl web browser plugin. Enter information about a home
loan in the three entries, then type Return in any of the entries or
click on the
Source:The Tcl script for this application is about 270 lines long, including comments: # mortgage.tcl -- # # This file contains a script that displays a simple spreadsheet for # calculating interest payments on a mortgage or other loan. It is # intended to be used in conjunction with the Tcl/Tk plugin for # Netscape. # # SCCS: @(#) mortgage.tcl 1.2 96/06/05 17:43:53 eval destroy [winfo child .] # The following variables provide configuration information that # controls the display. set axisFont {Helvetica 12 bold} set titleFont {Helvetica 18 bold} set barColor #7ab8cd set accentColor #90daf3 set msgColor red set graph(width) [winfo pixels . 5i] set graph(height) [winfo pixels . 3i] set graph(xOrigin) [winfo pixels . 0.8i] set graph(yOrigin) [winfo pixels . 3.5i] # monthly -- # # Given a loan principal, a term in years, and an annual percentage # rate, returns the monthly payment. # # Arguments: # principal - The amount borrowed. # term - The time over which the loan must be repaid, in used. # rate - The interest rate, in percent. proc monthly {principal term rate} { set months [expr round($term*12)] set interest [expr $rate/1200.0] set principal [expr (double($principal))] if {$interest <= 0} { set payment [expr $principal/$months] } else { set annuity [expr (1 - pow(1+$interest, -$months))/$interest] set payment [expr $principal/$annuity] } return $payment } # validate -- # This procedure is invoked to make sure that an entry contains a valid # number. If so, it returns 1. If not, it sets the global variable # "msg" with a diagnostic message, moves the input focus to the # offending entry, and returns 0. # # Arguments: # entry - Name of the entry widget. # info - Description of the value stored in the entry, such # as "principal"; used in diagnostic messages. proc validate {entry info} { global msg set value [$entry get] if {$value == ""} { set msg "Please enter a $info" focus $entry return 0 } if {$value <= 0} { set msg "Please enter a positive $info" focus $entry return 0 } if [catch {expr {2 * $value}}] { set msg "The $info isn't a proper number; please re-enter" $entry selection range 0 end focus $entry return 0 } if {($info == "loan period") && ($value > 60)} { set msg "The $info is too long - use a $info of less than 60 years" $entry selection range 0 end focus $entry return 0 } if {($info == "rate of interest") && ($value > 20)} { set msg "You can't afford an interest rate of $value%!" $entry selection range 0 end focus $entry return 0 } return 1 } # calculate -- # # This procedure is invoked whatever Return is typed in any of the entry # widgets. It validates the contents of the entries, and recalculates # the payment information. # # Arguments: # None. proc calculate {} { global principal term rate msg graph if {![validate .f1.e "principal amount"] || ![validate .f2.e "loan period"] || ![validate .f3.e "rate of interest"]} { return } set msg "Calculating " update idletasks set payment [monthly $principal $term $rate] .pay configure -text "Monthly payment is [format {$%.2f} $payment]." set msg "" plot .c $graph(xOrigin) $graph(yOrigin) $graph(width) $graph(height) $principal $term $rate $payment } # yaxis -- # # This procedure picks an an appropriate scale factor for the y-axis of # a plot, and draws the y axis, tick marks, and labels in a canvas. It # returns the vertical scale factor to use for the plot (what to # multiply a y-value by to get a canvas coordinate). # # Arguments: # c - Canvas in which to draw. # xOrigin, yOrigin - Location of the origin for the plot. # height - Height of the y-axis, in pixels. # yMax - Maximum y-coordinate that will be displayed. # format - Format string to use when displaying tick labels # (such as %.2f). proc yaxis {c xOrigin yOrigin height yMax format} { global axisFont set log [expr log10($yMax)] set unit [expr pow(10, floor($log))] set factor [expr $yMax/$unit] if {$factor <= 2.0} { set unit [expr $unit/5.0] } elseif {$factor < 5.0} { set unit [expr $unit/2.0] } set nUnits [expr floor(($yMax + $unit - 1)/$unit)] set scale [expr $height/($unit*$nUnits)] $c create line $xOrigin $yOrigin $xOrigin [expr $yOrigin-$height] \ -width 1 -fill black set x2 [expr $xOrigin + 5] for {set i 0} {$i <= $nUnits} {incr i} { set y [expr $i*$unit] set yCanv [expr $yOrigin - $y * $scale] $c create line $xOrigin $yCanv $x2 $yCanv -width 1 -fill black $c create text [expr $xOrigin-2] $yCanv -text [format $format $y] \ -anchor e -font $axisFont -fill black } return $scale } # xaxis -- # # This procedure is similar to yaxis above except that it handles the # x-axis instead of a y-axis. # # Arguments: # c - Canvas in which to draw. # xOrigin, yOrigin - Location of the origin for the plot. # width - Width of the y-axis, in pixels. # xMax - Maximum x-coordinate that will be displayed. # format - Format string to use when displaying tick labels # (such as %.2f). proc xaxis {c xOrigin yOrigin width xMax format} { global axisFont set log [expr log10($xMax)] set unit [expr pow(10, floor($log))] set factor [expr $xMax/$unit] if {$factor <= 2.0} { set unit [expr $unit/5.0] } elseif {$factor < 5.0} { set unit [expr $unit/2.0] } set nUnits [expr floor(($xMax + $unit - 1)/$unit)] set scale [expr $width/($unit*$nUnits)] $c create line $xOrigin $yOrigin [expr $xOrigin+$width] $yOrigin \ -width 1 -fill black set y2 [expr $yOrigin - 5] for {set i 0} {$i <= $nUnits} {incr i} { set x [expr $i*$unit] set xCanv [expr $xOrigin + $x * $scale] $c create line $xCanv $yOrigin $xCanv $y2 -width 1 -fill black $c create text $xCanv [expr $yOrigin+2] -text [format $format $x] \ -anchor n -font $axisFont -fill black } return $scale } # plot -- # # Given information about a loan and a canvas to draw in, create a bar # chart in the canvas of principal paid, as a function of time, with # active bars that provide additional information when the mouse passes # over them. # # Arguments: # c - Canvas in which to draw. # xOrigin, yOrigin - Location of the origin for the plot. # width, height - Dimensions of the plot, in pixels. # principal - Initial loan amount. # term - Duration of loan, in years. # rate - Interest rate for the loan, in percent. # payment - Monthly payment. proc plot {c xOrigin yOrigin width height principal term rate payment} { global barColor accentColor titleFont axisFont $c delete all set xScale [xaxis $c $xOrigin $yOrigin $width $term %.0f] set yScale [yaxis $c $xOrigin $yOrigin $height $principal %.0f] set x [expr $xOrigin + $width/2] $c create text $x [expr $yOrigin - $height - 5] \ -text {Principal Paid ($)} -font $titleFont -anchor s $c create text $x [expr $yOrigin + [winfo pixels $c 16p]] \ -text "Year Of Loan" -font $axisFont -anchor n set orig $principal set rate [expr $rate/1200.0] for {set year 1} {$year <= $term} {incr year} { for {set month 0} {$month < 12} {incr month} { set principal [expr $principal + ($principal*$rate) - $payment] } if {$year == 1} { set plural "" } else { set plural "s" } set princPaid [expr $orig - $principal] set msg [format {After %d year%s you will have paid $%.0f\ of principal and $%.0f of interest.} $year $plural $princPaid \ [expr 12*$year*$payment - $princPaid]] set x2 [expr $xOrigin + $xScale*$year] set x1 [expr $x2 - $xScale] set y1 [expr $yOrigin - $yScale*$princPaid] if {$y1 > ($yOrigin-1)} { set y1 [expr $yOrigin - 1] } set id [$c create rectangle $x1 $y1 $x2 $yOrigin -fill $barColor \ -outline black -width 1 -tags bar] $c bind $id <Enter> [list set msg $msg] } $c bind bar <Enter> "$c itemconfigure current -fill $accentColor" $c bind bar <Leave> "$c itemconfigure current -fill $barColor" $c lower bar } # resize -- # # This procedure is invoked when the canvas window used for plotting # changes size. It recalculates the geometry of the plot to take # advantage of all the space available in the canvas. # # Arguments: # c - Name of the canvas widget that changed size. proc resize c { global graph set graph(width) [expr [winfo width $c] - [winfo pixels $c 1i]] set graph(height) [expr [winfo height $c] - [winfo pixels $c 1i]] set graph(xOrigin) [winfo pixels $c .8i] set graph(yOrigin) [expr $graph(height) + [winfo pixels $c .5i]] } frame .f1 frame .f2 frame .f3 pack .f1 .f2 .f3 -side top -anchor w -fill x label .f1.l -text "Enter principal amount ($):" -width 25 -anchor w entry .f1.e -textvariable principal bind .f1.e <Return> calculate pack .f1.l .f1.e -side left label .f2.l -text "Enter loan period (years):" -width 25 -anchor w entry .f2.e -textvariable term bind .f2.e <Return> calculate pack .f2.l .f2.e -side left button .f2.calculate -text Calculate -command calculate pack .f2.calculate -side right -expand 1 label .f3.l -text "Enter interest rate (%):" -width 25 -anchor w entry .f3.e -textvariable rate bind .f3.e <Return> calculate pack .f3.l .f3.e -side left label .pay -anchor w pack .pay -side top -fill x canvas .c -width 10 -height 10 -relief sunken -bd 2 pack .c -side top -anchor w -fill both -expand yes bind .c <Configure> {resize .c} label .msg -textvariable msg -anchor w -fg $msgColor pack .msg -side top -fill x |