It was pretty simple actually.
Calculating the Monthly Payment given the interest rate and term of the loan can be done with this formula:
M = P [ i(1 + i)^n ] / [ (1 + i)^n – 1]
- M = Total monthly payment.
- P = The total amount of your loan.
- I = Your interest rate, as a monthly percentage.
- N = The total amount of months in your timeline for paying off your mortgage.
I used the following code to calculate the monthly payment:
const TOTAL_NUMBER_OF_MONTHS = Number(data.loanTerm) * 12;
const MONTHLY_INTEREST_RATE = Number(data.interestRate) / 100 / 12;
const MONTHLY_PAYMENT = PRINCIPAL * (MONTHLY_INTEREST_RATE * (1 + MONTHLY_INTEREST_RATE) ** TOTAL_NUMBER_OF_MONTHS) / ((1 + MONTHLY_INTEREST_RATE) ** TOTAL_NUMBER_OF_MONTHS - 1);
Well actually that was fairly simple!
All I had to do was use a javascript library called ChartJS
That library provides a utility for using a canvas element on the page to display a chart!
Then it was just a matter of calculating the principal paid and interest paid over the months, reducing that down to a useful number of data points, and sending it to the constructor for the Chart Element.
I used the following code to create the graph:
export const createMyChart = (ctx: HTMLCanvasElement, dta: ChartData[], loanTerm:number ) =>{
if(Chart.getChart(ctx)){
Chart.getChart(ctx).destroy();
}
return new Chart(ctx, {
type: "line",
data: {
labels: dta.map((d) => d.year),
datasets: [
{
label: "Remaining Balance",
data: dta.map((d) => d.remainingPrincipal),
borderColor: "#0d6efd",
backgroundColor:"#0d6efd",
type: "line",
order: 0,
},
{
label: "Principal",
data: dta.map((d) => d.monthlyPrincipal),
borderColor: "#074297",
backgroundColor: "#074297",
type: "bar",
yAxisID: "payments",
},
{
label: "Interest",
data: dta.map((d) => d.interest),
backgroundColor: "#03214b",
borderColor: "#03214b",
type: "bar",
yAxisID: "payments",
},
],
},
options: {
responsive: true,
maintainAspectRatio: true,
scales: {
x: {
stacked: true,
title: {
display: true,
text: "Year",
},
ticks: {
stepSize: loanTerm,
},
},
y: {
beginAtZero: true,
stacked: false,
title: {
display: true,
text: "Balance",
},
},
payments: {
min: 0,
stacked: true,
beginAtZero: true,
position: "right",
grid: {
drawOnChartArea: false,
},
title: {
display: true,
text: "Monthly Payments",
},
},
},
plugins: {
title: {
display: true,
text: "Mortgage Over Years",
},
},
interaction:{
mode: 'index',
}
},
});
}
This is basically a function that takes in the canvas element from the page, checks to see if there's already a graph, and, if there is, destroy it and recreate the new graph with the data that's been passed in.
Well that's the tricky part really. I'm really working on trying to figure out "what makes a good test", but for now I wanted to test a function I used to reduce the size of a dataset to a useful number of data points. I used the following code to test it:
const {describe, expect, it} = require("@jest/globals");
const {everyNthElement} = require("../src/utilities");
describe("everyNthElement", () => {
it("should return every nth element", () => {
const array = [1, 2, 3, 4, 5, 6, 7, 8, 9];
const result = everyNthElement(array, 3);
expect(result).toEqual([1, 4, 7]);
});
it('should return the first element if n is 1', () => {
const array = [1, 2, 3, 4, 5, 6, 7, 8, 9];
const result = everyNthElement(array, 1);
expect(result).toEqual([1]);
});
});
I guess admittedly I ran another test but that was just for the configuration of jest! Here's that code
const {describe, expect, test} = require("@jest/globals")
const mySum = (a, b)=> a + b;
describe("mySum", () => {
test("should return the sum of two numbers", () => {
expect(mySum(1, 2)).toBe(3);
});
});