PageSourceSearch

https://mckaymortgageco.com/loansites_apps/wwd-calculators/calcula…tgage_with_pmi_adjustment.js?ver=1749448048

js mckaymortgageco.com collected 2026-09-30 12:40:51 UTC 40,489 bytes, 1,090 lines download raw bytes

1window.calculatorOutputs = window.calculatorOutputs || {};
2window.calculatorHandlers.arm_mortgage_with_pmi_adjustment = function ($form, $container, response = null) {
3  function show_error(text) {
4    let errorContainer = $container.find(".out-error-message");
5    let resultsContainer = $container.find(".wwd-calculator-results");
6
7    if (text === "") {
8      resultsContainer.show();
9      errorContainer.hide();
10    } else {
11      resultsContainer.hide();
12      errorContainer.text(text).show();
13    }
14  }
15
16  // Get input values
17  var arm_type = $form.find('input[name="arm_type"]:checked').val();
18  var homeValue = parseFloat($form.find("#home_value").val().replace(/,/g, ""));
19  var downPaymentPercentage = parseFloat($form.find("#down_payment_percentage").val().replace(/,/g, "")) / 100;
20  var downPaymentAmount = parseFloat($form.find("#down_payment_amount").val().replace(/,/g, ""));
21  var interestRate = parseFloat($form.find("#interest_rate").val().replace(/,/g, "")) / 100;
22  var loanTerm = parseInt($form.find('input[name="loan_term"]:checked').val().replace(/,/g, ""));
23  var includePMI = $form.find("#include_pmi").is(":checked");
24  var includeTaxesInsurance = $form.find("#include_taxes_insurance").is(":checked");
25  var propertyTaxPercentage = parseFloat($form.find("#property_tax_percentage").val().replace(/,/g, "")) / 100;
26  var homeownersInsurance = parseFloat($form.find("#homeowners_insurance").val().replace(/,/g, ""));
27  var homeownersInsurancePercentage = parseFloat($form.find("#homeowners_insurance_percentage").val().replace(/,/g, "")) / 100;
28  var hoaDues = parseFloat($form.find("#hoa_dues").val().replace(/,/g, ""));
29
30  // var homeAppreciationRate = parseFloat($form.find("#home_appreciation").val().replace(/,/g, "")) / 100;
31  var expectedAdjustment = parseFloat($form.find("#expected_adjustment").val().replace(/,/g, "")) / 100;
32  var rateDirection = $form.find('input[name="rate_direction"]:checked').val();
33  var maxAdjustment = parseFloat($form.find("#max_adjustment").val().replace(/,/g, "")) / 100;
34  var rateFloor = parseFloat($form.find("#rate_floor").val().replace(/,/g, "")) / 100;
35  // Get ARM-specific inputs
36  var monthsBetweenAdjustments = parseInt($form.find("#months_between_adjustments").val());
37  var monthsBeforeFirstAdjustment = parseInt($form.find("#months_before_first_adju
37stment").val());
38
39  // Perform calculations
40  var downPayment = downPaymentAmount || homeValue * downPaymentPercentage;
41  var loanAmount = homeValue - downPayment;
42  var monthlyInterestRate = interestRate / 12;
43  var numberOfPayments = loanTerm * 12;
44
45  // Calculate monthly principal and interest
46  var monthlyPandI = (loanAmount * monthlyInterestRate * Math.pow(1 + monthlyInterestRate, numberOfPayments)) / (Math.pow(1 + monthlyInterestRate, numberOfPayments) - 1);
47
48  // Calculate LTV ratio
49  var ltvRatio = (loanAmount / homeValue) * 100;
50
51  // Calculate PMI rate based on LTV ratio and loan term
52  function getPMIRate(ltv, loanTerm) {
53    if (ltv <= 80) return 0;
54    if (ltv > 97) return 0;
55
56    const creditScore = jQuery('input[name="credit_score"]:checked').val();
57    let ltvRange;
58
59    if (ltv > 97) {
60      ltvRange = "97_plus";
61    } else if (ltv > 95) {
62      ltvRange = "95_97";
63    } else if (ltv > 90) {
64      ltvRange = "90_95";
65    } else if (ltv > 85) {
66      ltvRange = "85_90";
67    }
68
69    const rateKey = `ltv_${ltvRange}_credit_score_${creditScore}`;
70
71    if (WWD_Calculators && WWD_Calculators.ltv_rate_adjustments && WWD_Calculators.ltv_rate_adjustments[rateKey]) {
72      return parseFloat(WWD_Calculators.ltv_rate_adjustments[rateKey]);
73    }
74
75    return 0;
76  }
77
78  // Calculate monthly PMI
79  var annualPMIRate = getPMIRate(ltvRatio, loanTerm);
80  var monthlyPMI = (loanAmount * annualPMIRate) / 100 / 12;
81
82  var currentHomeValue = homeValue;
83
84  // Calculate monthly taxes and insurance
85  var monthlyPropertyTax = (homeValue * propertyTaxPercentage) / 12;
86  var monthlyHomeownersInsurance = homeownersInsurance / 12;
87  var monthlyTaxesAndHOA = monthlyPropertyTax + hoaDues;
88
89  // Calculate total monthly payment
90  var totalMonthlyPayment = includePMI ? monthlyPandI + monthlyPMI : monthlyPandI;
91  totalMonthlyPayment += includeTaxesInsurance ? monthlyTaxesAndHOA + monthlyHomeownersInsurance : 0;
92
93  // Calculate amortization schedule
94  var amortizationSchedule = [];
95  var remainingBalance = loanAmount;
96  var totalPrincipal = 0;
97  var totalInterest = 0;
98  var currentInterestRate = interestRate;
99  var monthlyInterestRate = currentInterestRate / 12;
100  var currentMonthlyPMI = monthlyPMI;
101
102  for (var month = 1; month <= numberOfPayments; month++) {
103    // Check if it's time for a rate adjustment
104    if (month > monthsBeforeFirstAdjustment && (month - monthsBeforeFirstAdjustment) % monthsBetweenAdjustments === 0) {
105      // Adjust the interest rate
106      if (rateDirection === "up") {
107        currentInterestRate = Math.min(currentInterestRate + expectedAdjustment, interestRate + maxAdjustment);
108      } else {
109        currentInterestRate = Math.max(currentInterestRate - expectedAdjustment, rateFloor);
110      }
111      monthlyInterestRate = currentInterestRate / 12;
112
113      // Recalculate monthly payment
114      var remainingMonths = numberOfPayments - month + 1;
115      monthlyPandI = (remainingBalance * monthlyInterestRate * Math.pow(1 + monthlyInterestRate, remainingMonths)) / (Math.pow(1 + monthlyInterestRate, remainingMonths) - 1);
116    }
117
118    var interestPayment = remainingBalance * monthlyInterestRate;
119    var principalPayment = monthlyPandI - interestPayment;
120    remainingBalance -= principalPayment;
121
122    totalPrincipal += principalPayment;
123    totalInterest += interestPayment;
124
125    if (month % 12 === 1 && month !== 1) {
126      currentHomeValue *= 1;
127
128      // recalculate property tax
129      monthlyPropertyTax = (currentHomeValue * propertyTaxPercentage) / 12;
130      monthlyTaxesAndHOA = monthlyPropertyTax + hoaDues;
131      // recalculate homeowners insurance
132      monthlyHomeownersInsurance = (currentHomeValue * homeownersInsurancePercentage) / 12;
133    }
134
135    var currentLtvRatio = (remainingBalance / homeValue) * 100;
136
137    // Check if PMI should be removed
138    if (currentLtvRatio <= 78 && currentMonthlyPMI > 0) {
139      currentMonthlyPMI = 0;
140    }
141
142    amortizationSchedule.push({
143      month: month,
144      payment: monthlyPandI,
145      principalPayment: principalPayment,
146      interestPayment: interestPayment,
147      monthlyPMI: currentMonthlyPMI ? currentMonthlyPMI : 0,
148      monthlyPropertyTax: includeTaxesInsurance ? monthlyPropertyTax : 0,
149      monthlyHomeownersInsurance: includeTaxesInsurance ? monthlyHomeownersInsurance : 0,
150      monthlyHOADues: includeTaxesInsurance ? hoaDues : 0,
151      monthlyTaxesHoa: includeTaxesInsurance ? monthlyTaxesAndHOA : 0,
152      remainingBalance: remainingBalance,
153      totalPrincipalPaid: totalPrincipal,
154      totalInterestPaid: totalInterest,
155      interestRate: currentInterestRate * 100,
156      homeValue: currentHomeValue,
157    });
158  }
159
160  // Prepare result object
161  var result = {
162    armType: arm_type,
163    homeValue: wwd_moneyFormat(homeValue, 0),
164    loanAmount: wwd_moneyFormat(loanAmount, 0),
165    downPayment: wwd_moneyFormat(downPayment, 0),
166    interestRate: wwd_moneyFormat(interestRate * 100, 2) + " %",
167    loanTerm: loanTerm + " years",
168    loanTermMonths: loanTerm * 12,
169    loanTermYears: loanTerm,
170    taxesInsuranceIncluded: includeTaxesInsurance ? "Yes" : "No",
171    pmiIncluded: includePMI ? "Yes" : "No",
172    propertyTax: wwd_moneyFormat(propertyTaxPercentage * 100, 2) + " %",
173    homeownersInsurance: wwd_moneyFormat(homeownersInsurance, 0),
174    mortgageInsurance: wwd_moneyFormat(monthlyPMI, 0),
175    hoaDues: wwd_moneyFormat(hoaDues, 0),
176    monthlyPMI: wwd_moneyFormat(monthlyPMI, 0),
177    monthlyPandI: wwd_moneyFormat(monthlyPandI, 0),
178    monthlyTaxesAndHOA: wwd_moneyFormat(monthlyTaxesAndHOA, 0),
179    monthlyHomeownersInsurance: wwd_moneyFormat(monthlyHomeownersInsurance, 0),
180    totalMonthlyPayment: wwd_moneyFormat(totalMonthlyPayment, 0),
181    amortizationSchedule: amortizationSchedule,
182    totalPrincipal: wwd_moneyFormat(totalPrincipal, 0),
183    totalInterest: wwd_moneyFormat(totalInterest, 0),
184  };
185
186  // Update the UI with the results
187  updateUI(result, $container);
188
189  // Clear any previous errors
190  show_error("");
191
192  function updateUI(result, $container) {
193    // Add to window.wwd_results
194    window.wwd_results = window.wwd_results || {};
195
196    for (let key in result) {
197      if (!Array.isArray(result[key])) {
198        window.wwd_results[key] = result[key];
199      }
200    }
201    // Summary tab
202    $container.find("#out-p_and_i").text(result.monthlyPandI);
203    $container.find("#out-pmi").text(result.monthlyPMI);
204    $container.find("#out-taxes-hoa").text(result.monthlyTaxesAndHOA);
205    $container.find("#out-homeowners-insurance").text(result.monthlyHomeownersInsurance);
206    $container.find("#out-purchase-price").text(result.homeValue);
207    $container.find("#out-down-payment").text(result.downPayment);
208    $container.find("#out-total-loan-amount").text(result.loanAmount);
209    $container.find(".out-term-years").text(result.loanTermYears);
210
211    // if PMI is not included hide the PMI row.
212    if (result.pmiIncluded === "No") {
213      $container.find(".pmi-out-wrapper").hide();
214    } else {
215      $container.find(".pmi-out-wrapper").show();
216    }
217
218    // if taxes and insurance is not included hide the taxes and insurance row.
219    if (result.taxesInsuranceIncluded === "No") {
220      $container.find(".taxes-hoa-out-wrapper").hide();
221      $container.find(".insurance-out-wrapper").hide();
222    } else {
223      $container.find(".taxes-hoa-out-wrapper").show();
224      $container.find(".insurance-out-wrapper").show();
225    }
226
227    // Convert ARM type to display format
228    const armTypeMap = {
229      "10_1_arm": "10/1 ARM",
230      "7_1_arm": "7/1 ARM",
231      "5_1_arm": "5/1 ARM",
232      "3_1_arm": "3/1 ARM",
233      "10_6_month_arm": "10/6 month ARM",
234      "7_6_month_arm": "7/6 month ARM",
235      "5_6_month_arm": "5/6 month ARM",
236      "3_6_month_arm": "3/6 month ARM",
237    };
238    const displayArmType = armTypeMap[result.armType];
239
240    // Full Report tab
241    $container.find("#out-full-report-arm-type").text(displayArmType);
242    $container.find("#out-full-report-estimated-payment").text(result.totalMonthlyPayment);
243    $container.find("#out-full-report-loan-amount").text(result.loanAmount);
244    $container.find("#out-full-report-down-payment").text(result.downPayment);
245    $container.find("#out-full-report-interest-rate").text(result.interestRate);
246    $container.find("#out-full-report-loan-term").text(result.loanTerm);
247    $container.find("#out-full-report-taxes-insurance-included").text(result.taxesInsuranceIncluded);
248    $container.find("#out-full-report-property-tax").text(result.propertyTax + " /yr");
249    $container.find("#out-full-report-homeowners-insurance").text(result.homeownersInsurance + " /yr");
250    $container.find("#out-full-report-mortgage-insurance").text(result.monthlyPMI);
251    $container.find("#out-full-report-hoa-dues").text(result.hoaDues + " /mo");
252    $container.find("#out-full-report-principal-payments").text(result.totalPrincipal);
253    $container.find("#out-full-report-interest-payments").text(result.totalInterest);
254
255    // Update the max value for #yearSlider
256    const termInMonths = parseInt(result.loanTermMonths);
257    $("#yearSlider").attr("max", termInMonths);
258    $("#yearSlider").val(0);
259    $(".out-term-years").text(result.loanTermYears);
260    $("#yearSliderFill").css("width", "0%");
261    $("#yearSliderThumb").css("left", "0%");
262
263    // Update payment breakdown chart
264    updatePaymentBreakdownChart(result, $container.find("#out-full-report-payment-breakdown"), "full-report-chart");
265    updatePaymentBreakdownChart(result, $container.find("#out-payment-breakdown"), "summary-chart");
266
267    // Update amortization chart
268    updateAmortizationChart(result, $container.find("#out-amortization-chart"));
269
270    // Update principal vs interest chart
271    updatePrincipalVsInterestChart(result, $container.find("#out-principal-vs-interest-chart"));
272
273    // Update amortization schedule
274    updateAmortizationSchedule(result, $container.find("#out-amortization-schedule-table"));
275  }
276
277  function updatePaymentBreakdownChart(result, $container, chartId) {
278    console.log("Updating chart with ID:", chartId);
279    console.log("Existing charts:", window.paymentBreakdownCharts);
280
281    // Destroy existing chart if it exists
282    if (window.paymentBreakdownCharts && window.paymentBreakdownCharts[chartId]) {
283      console.log("Attempting to destroy chart:", chartId);
284      try {
285        window.paymentBreakdownCharts[chartId].destroy();
286        console.log("Chart destroyed successfully");
287      } catch (error) {
288        console.error("Error destroying chart:", error);
289      }
290      delete window.paymentBreakdownCharts[chartId];
291    } else {
292      console.log("No existing chart found to destroy");
293    }
294
295    const amortizationSchedule = result.amortizationSchedule;
296
297    console.log(amortizationSchedule);
298
299    function updateChartData(monthIndex) {
300      console.log("Updating chart data for month:", monthIndex);
301      const scheduleItem = amortizationSchedule[monthIndex];
302
303      if (!scheduleItem) {
304        console.error("No schedule item found for month:", monthIndex);
305        return;
306      }
307
308      // Parse the values, removing currency symbols and commas
309      const interestRate = parseFloat(scheduleItem.interestRate);
310      const principal = parseFloat(scheduleItem.principalPayment);
311      const interest = parseFloat(scheduleItem.interestPayment);
312      const pmi = parseFloat(scheduleItem.monthlyPMI);
313      const taxesAndHoa = parseFloat(scheduleItem.monthlyTaxesHoa);
314      const homeownersInsurance = parseFloat(scheduleItem.monthlyHomeownersInsurance);
315
316      // Update the chart data
317      chart.data.datasets[0].data = [principal, interest, pmi, taxesAndHoa, homeownersInsurance];
318
319      const totalMonthlyPayment = principal + interest + pmi + taxesAndHoa + homeownersInsurance;
320      // Update the centerTextPlugin options
321      chart.options.plugins.centerText = {
322        text: wwd_moneyFormat(totalMonthlyPayment, 0),
323      };
324
325      chart.update();
326
327      // Update output elements
328      const currentDate = new Date();
329      const futureDate = new Date(currentDate.getFullYear(), currentDate.getMonth() + monthIndex, 1);
330      const year = Math.floor(monthIndex / 12) + 1;
331      const month = (monthIndex % 12) + 1;
332
333      $(".current-interest-rate").text(interestRate.toFixed(3) + "%");
334      $(".out-current-year").text(year);
335      $(".out-current-month").text(month);
336      $(".current-year-savings").text("$" + wwd_moneyFormat(totalMonthlyPayment, 0));
337      $(".out-current-month-year").text(futureDate.toLocaleString("default", { month: "long", year: "numeric" }));
338
339      // Update legend output
340      $("#out-monthly-principal").text(wwd_moneyFormat(principal, 0));
341      $("#out-monthly-interest").text(wwd_moneyFormat(interest, 0));
342      $("#out-pmi").text(wwd_moneyFormat(pmi, 0));
343      $("#out-taxes-hoa").text(wwd_moneyFormat(taxesAndHoa, 0));
344      $("#out-homeowners-insurance").text(wwd_moneyFormat(homeownersInsurance, 0));
345
346      // Show/hide PMI row based on whether PMI is included
347      if (pmi > 0) {
348        $(".pmi-out-wrapper").show();
349      } else {
350        $(".pmi-out-wrapper").hide();
351      }
352
353      // Show/hide taxes and insurance rows based on whether they are included
354      if (taxesAndHoa > 0 || homeownersInsurance > 0) {
355        $(".taxes-hoa-out-wrapper").show();
356        $(".insurance-out-wrapper").show();
357      } else {
358        $(".taxes-hoa-out-wrapper").hide();
359        $(".insurance-out-wrapper").hide();
360      }
361
362      console.log("Chart data updated successfully");
363    }
364
365    // Create the data for the chart
366    const data = {
367      labels: ["Principal", "Interest", "PMI", "Taxes and HOA", "Homeowners Insurance"],
368      datasets: [
369        {
370          data: [0, 0, 0, 0, 0],
371          backgroundColor: [WWD_Calculators.colors.principal_color, WWD_Calculators.colors.interest_color, WWD_Calculators.colors.pmi_color, WWD_Calculators.colors.taxes_hoa_color, WWD_Calculators.colors.property_insurance_color],
372          hoverOffset: 20,
373        },
374      ],
375    };
376
377    // Custom plugin for center text
378    const centerTextPlugin = {
379      id: "centerText",
380      options: {
381        text: "0",
382      },
383      afterDraw: (chart, args, options) => {
384        const width = chart.width;
385        const height = chart.height;
386        const ctx = chart.ctx;
387        ctx.restore();
388
389        // Title text
390        const titleFontSize = (height / 360).toFixed(2);
391        ctx.font = `${titleFontSize}em "Plus Jakarta Sans"`;
392        ctx.fillStyle = "#000";
393        ctx.textBaseline = "middle";
394        ctx.textAlign = "center";
395        ctx.fillText("Your Estimated", width / 2, height / 2.3 - height / 12);
396        ctx.fillText("Monthly Payment:", width / 2, height / 2.24 - height / 24);
397
398        // Amount text
399        const amountFontSize = (height / 110).toFixed(2);
400        ctx.font = `bold ${amountFontSize}em "Plus Jakarta Sans"`;
401        ctx.fillStyle = "#555";
402
403        // Render dollar sign
404        const dollarSignFontSize = (height / 240).toFixed(2);
405        ctx.font = `${dollarSignFontSize}em "Plus Jakarta Sans"`;
406        ctx.fillText("$", width / 2.45 - ctx.measureText(result.totalMonthlyPayment).width / 2 - ctx.measureText("$").width / 2, height / 2.1 + height / 24);
407
408        // Render amount
409        ctx.font = `bold ${amountFontSize}em "Plus Jakarta Sans"`;
410        ctx.fontFamily = "Plus Jakarta Sans";
411        ctx.fillStyle = "#000";
412        const text = options.text;
413        // const text = result.totalMonthlyPayment.replace(/^\$/, "").trim();
414        ctx.fillText(text, width / 2.15 + ctx.measureText("$").width / 2, height / 2 + height / 24);
415
416        // ctx.save();
417        ctx.restore();
418      },
419    };
420
421    // Chart configuration
422    const config = {
423      type: "doughnut",
424      data: data,
425      options: {
426        responsive: true,
427        plugins: {
428          legend: {
429            display: false,
430          },
431          title: {
432            display: false,
433          },
434          tooltip: {
435            callbacks: {
436              label: function (context) {
437                let label = context.label || "";
438                if (label) {
439                  label += ": ";
440                }
441                if (context.parsed !== null) {
442                  label += "$" + window.wwd_moneyFormat(context.parsed, 0);
443                }
444                return label;
445              },
446            },
447          },
448        },
449        cutout: "70%",
450        onHover: (event, chartElement) => {
451          event.native.target.style.cursor = chartElement[0] ? "pointer" : "default";
452        },
453        layout: {
454          padding: 20,
455        },
456      },
457      plugins: [centerTextPlugin],
458    };
459
460    // Create the chart
461    if (!window.paymentBreakdownCharts) {
462      window.paymentBreakdownCharts = {};
463    }
464    const chart = new Chart($container[0], config);
465    window.paymentBreakdownCharts[chartId] = chart;
466    console.log("New chart created with ID:", chartId);
467
468    // Initialize the chart with the first month's data
469    updateChartData(0);
470
471    // Remove any existing event listener
472    $("#yearSlider").off("input.updateChartData");
473    $("#yearSlider").val(0);
474
475    // Add event listener for the year slider with a namespace
476    $("#yearSlider").on("input.updateChartData", function () {
477      const selectedMonth = parseInt($(this).val());
478      updateChartData(selectedMonth - 1);
479    });
480
481    // Store the updateChartData function on the chart object
482    chart.updateChartData = updateChartData;
483  }
484
485  function updateAmortizationChart(result, $container) {
486    // Destroy existing chart if it exists
487    if (window.amortizationChart) {
488      window.amortizationChart.destroy();
489    }
490
491    const schedule = result.amortizationSchedule;
492    const labels = schedule.map((entry) => entry.month);
493    const principalData = schedule.map((entry) => entry.totalPrincipalPaid);
494    const interestData = schedule.map((entry) => entry.totalInterestPaid);
495    const remainingBalanceData = schedule.map((entry) => entry.remainingBalance);
496
497    const principalColor = WWD_Calculators.colors.principal_color;
498    const interestColor = WWD_Calculators.colors.interest_color;
499    const remainingBalanceColor = WWD_Calculators.colors.remaining_balance_color;
500
501    const data = {
502      labels: labels,
503      datasets: [
504        {
505          label: "Principal",
506          data: principalData,
507          borderColor: principalColor,
508          backgroundColor: principalColor,
509          fill: false,
510          tension: 0.1,
511        },
512        {
513          label: "Interest",
514          data: interestData,
515          borderColor: interestColor,
516          backgroundColor: interestColor,
517          fill: false,
518          tension: 0.1,
519        },
520        {
521          label: "Remaining",
522          data: remainingBalanceData,
523          borderColor: remainingBalanceColor,
524          backgroundColor: remainingBalanceColor,
525          fill: false,
526          tension: 0.1,
527        },
528      ],
529    };
530
531    const config = {
532      type: "line",
533      data: data,
534      options: {
535        responsive: true,
536        plugins: {
537          legend: {
538            position: "top",
539          },
540          title: {
541            display: false,
542            text: "Amortization Schedule",
543          },
544          tooltip: {
545            mode: "index",
546            intersect: false,
547            callbacks: {
548              title: function (context) {
549                return "Month " + context[0].label;
550              },
551              label: function (context) {
552                let label = context.dataset.label || "";
553                if (label) {
554                  label += ": ";
555                }
556                if (context.parsed.y !== null) {
557                  label += new Intl.NumberFormat("en-US", { style: "currency", currency: "USD" }).format(context.parsed.y);
558                }
559                return label;
560              },
561            },
562          },
563        },
564        scales: {
565          x: {
566            title: {
567              display: true,
568              text: "Months",
569            },
570            ticks: {
571              callback: function (value, index, values) {
572                return value % 100 === 0 ? value + " mo" : "";
573              },
574            },
575          },
576          y: {
577            title: {
578              display: true,
579              text: "Amount",
580            },
581            ticks: {
582              callback: function (value, index, values) {
583                return "$" + value.toLocaleString();
584              },
585            },
586          },
587        },
588        elements: {
589          line: {
590            borderWidth: 1.5, // Make lines thinner
591          },
592          point: {
593            radius: 0,
594            hitRadius: 5,
595            hoverRadius: 3,
596          },
597        },
598      },
599    };
600
601    window.amortizationChart = new Chart($container[0], config);
602  }
603
604  function updatePrincipalVsInterestChart(result, $container) {
605    // Destroy existing chart if it exists
606    if (window.principalVsInterestChart) {
607      window.principalVsInterestChart.destroy();
608    }
609
610    const schedule = result.amortizationSchedule;
611    const labels = schedule.map((entry) => entry.month);
612    const principalData = schedule.map((entry) => entry.principalPayment);
613    const interestData = schedule.map((entry) => entry.interestPayment);
614
615    const principalColor = WWD_Calculators.colors.principal_color;
616    const interestColor = WWD_Calculators.colors.interest_color;
617
618    const data = {
619      labels: labels,
620      datasets: [
621        {
622          label: "Principal",
623          data: principalData,
624          backgroundColor: principalColor,
625          fill: true,
626        },
627        {
628          label: "Interest",
629          data: interestData,
630          backgroundColor: interestColor,
631          fill: true,
632        },
633      ],
634    };
635
636    const config = {
637      type: "line",
638      data: data,
639      options: {
640        responsive: true,
641        plugins: {
642          legend: {
643            position: "top",
644          },
645          title: {
646            display: false,
647            text: "Principal vs Interest",
648          },
649          tooltip: {
650            mode: "index",
651            intersect: false,
652            callbacks: {
653              title: function (context) {
654                return "Month " + context[0].label;
655              },
656              label: function (context) {
657                let label = context.dataset.label || "";
658                if (label) {
659                  label += ": ";
660                }
661                if (context.parsed.y !== null) {
662                  label += new Intl.NumberFormat("en-US", { style: "currency", currency: "USD" }).format(context.parsed.y);
663                }
664                return label;
665              },
666            },
667          },
668        },
669        scales: {
670          x: {
671            stacked: true,
672            title: {
673              display: true,
674              text: "Months",
675            },
676            ticks: {
677              callback: function (value, index, values) {
678                return value % 100 === 0 ? value + "mo" : "";
679              },
680            },
681          },
682          y: {
683            stacked: true,
684            title: {
685              display: true,
686              text: "Amount",
687            },
688            ticks: {
689              callback: function (value, index, values) {
690                return "$" + value.toLocaleString();
691              },
692            },
693          },
694        },
695        elements: {
696          line: {
697            tension: 0.4,
698          },
699          point: {
700            radius: 0,
701          },
702        },
703      },
704    };
705
706    window.principalVsInterestChart = new Chart($container[0], config);
707  }
708
709  function updateAmortizationSchedule(result, $container) {
710    const schedule = result.amortizationSchedule;
711    const itemsPerPage = 12;
712    let currentPage = 1;
713
714    function renderTable(page) {
715      const startIndex = (page - 1) * itemsPerPage;
716      const endIndex = startIndex + itemsPerPage;
717      const pageItems = schedule.slice(startIndex, endIndex);
718
719      let tableHtml = `
720      <div class="overflow-x-auto w-full wwd-amortization-table">
721        <table class="w-full text-sm text-left">
722          <thead>
723            <tr class="bg-gray-100">
724              <th class="px-4 py-2 text-[var(--wwd-heading-color)]">Month</th>
725              <th class="px-4 py-2 text-[var(--wwd-heading-color)]">Interest</th>
726              <th class="px-4 py-2 text-[var(--wwd-heading-color)]">Principal</th>
727              <th class="px-4 py-2 text-[var(--wwd-heading-color)]">Remaining</th>
728            </tr>
729          </thead>
730          <tbody>
731    `;
732
733      pageItems.forEach((item, index) => {
734        const rowClass = index % 2 === 0 ? "bg-white" : "bg-gray-50";
735        tableHtml += `
736        <tr class="${rowClass}">
737          <td class="px-4 py-2 text-[var(--wwd-body-color)]">${item.month}</td>
738          <td class="px-4 py-2 text-[var(--wwd-body-color)]">$${wwd_moneyFormat(item.interestPayment, 0)}</td>
739          <td class="px-4 py-2 text-[var(--wwd-body-color)]">$${wwd_moneyFormat(item.principalPayment, 0)}</td>
740          <td class="px-4 py-2 text-[var(--wwd-body-color)]">$${wwd_moneyFormat(item.remainingBalance, 0)}</td>
741        </tr>
742      `;
743      });
744
745      tableHtml += `
746          </tbody>
747        </table>
748      </div>
749    `;
750
751      $container.html(tableHtml);
752
753      // Add pagination
754      const totalPages = Math.ceil(schedule.length / itemsPerPage);
755      let paginationHtml = `
756        <div class="flex items-center justify-center mt-4 space-x-2">
757          <button class="pagination-prev px-3 py-1 rounded border ${page === 1 ? "opacity-50 cursor-not-allowed" : ""}">&lt;</button>
758      `;
759
760      for (let i = 1; i <= totalPages; i++) {
761        if (i === 1 || i === totalPages || (i >= page - 2 && i <= page + 2)) {
762          paginationHtml += `
763            <button class="pagination-page px-1.5 sm:px-3 py-1 rounded border text-xs sm:text-sm ${i === page ? "bg-[var(--wwd-primary-color)] text-white" : ""}">${i}</button>
764          `;
765        } else if (i === page - 3 || i === page + 3) {
766          paginationHtml += `<span class="px-1">...</span>`;
767        }
768      }
769
770      paginationHtml += `
771          <button class="pagination-next px-3 py-1 rounded border text-xs sm:text-sm ${page === totalPages ? "opacity-50 cursor-not-allowed" : ""}">&gt;</button>
772        </div>
773      `;
774
775      $container.append(paginationHtml);
776
777      // Add event listeners for pagination
778      $container.find(".pagination-prev").on("click", function () {
779        if (currentPage > 1) {
780          currentPage--;
781          renderTable(currentPage);
782        }
783      });
784
785      $container.find(".pagination-next").on("click", function () {
786        if (currentPage < totalPages) {
787          currentPage++;
788          renderTable(currentPage);
789        }
790      });
791
792      $container.find(".pagination-page").on("click", function () {
793        currentPage = parseInt(jQuery(this).text());
794        renderTable(currentPage);
795      });
796    }
797
798    renderTable(currentPage);
799  }
800};
801
802function initARMMortgageWithPMIAdjustmentCalculator() {
803  var hasARMMortgageWithPMIAdjustmentCalculator = jQuery(".wwd-calculator-form[data-type='arm_mortgage_with_pmi_adjustment']").length > 0;
804
805  if (!hasARMMortgageWithPMIAdjustmentCalculator) {
806    return;
807  }
808
809  jQuery(".wwd-calculator-wrapper.arm_mortgage_with_pmi_adjustment-calculator").each(function () {
810    $ = jQuery;
811    const $wrapper = $(this);
812    const $interestRateInput = $wrapper.find('input[name="interest_rate"]');
813    const $creditScoreInputs = $wrapper.find('input[name="credit_score"]');
814
815    $creditScoreInputs.on("change", function () {
816      const selectedCreditScore = $(this).val();
817      updateInterestRate(selectedCreditScore);
818    });
819
820    function updateInterestRate(creditScore) {
821      if (WWD_Calculators && WWD_Calculators.credit_score_rates) {
822        const rateKey = `arm_mortgage_credit_score_${creditScore}`;
823        const newRate = WWD_Calculators.credit_score_rates[rateKey];
824
825        if (newRate) {
826          $interestRateInput.val(newRate);
827          $interestRateInput.trigger("change");
828        }
829      }
830    }
831
832    // Initialize interest rate based on default credit score
833    const defaultCreditScore = $creditScoreInputs.filter(":checked").val();
834    updateInterestRate(defaultCreditScore);
835  });
836
837  let lastUpdated = "amount";
838
839  function updateDownPaymentAmount() {
840    var homeValue = parseFloat($("#home_value").val().replace(/,/g, "")) || 0;
841    var percentage = parseFloat($("#down_payment_percentage").val()) || 0;
842    var amount = homeValue * (percentage / 100);
843    $("#down_payment_amount").val(wwd_moneyFormat(amount, 0));
844  }
845
846  function updateDownPaymentPercentage() {
847    var homeValue = parseFloat($("#home_value").val().replace(/,/g, "")) || 1;
848    var amount = parseFloat($("#down_payment_amount").val().replace(/,/g, "")) || 0;
849    var percentage = (amount / homeValue) * 100;
850    $("#down_payment_percentage").val(Math.min(percentage, 100).toFixed(2));
851    $('input[data-type="range-input"][data-target="down_payment_percentage"]').val(Math.min(percentage, 80));
852    // update progress
853    var $range = $('input[data-type="range-input"][data-target="down_payment_percentage"]');
854    var val = parseFloat($("#down_payment_percentage").val());
855    var min = parseFloat($range.attr("min"));
856    var max = parseFloat($range.attr("max"));
857    var progress = Math.min(((val - min) / (max - min)) * 100, 100);
858    $range.val(val);
859    $range[0].style.setProperty("--range-progress", `${progress}%`);
860  }
861
862  function updateHomeValue() {
863    var homeValue = parseFloat($("#home_value").val().replace(/,/g, "")) || 0;
864    const $range = $('input[data-type="range-input"][data-target="home_value"]');
865    var min = parseFloat($range.attr("min"));
866    var max = parseFloat($range.attr("max"));
867    var progress = Math.min(((homeValue - min) / (max - min)) * 100, 100);
868    $range.val(homeValue);
869    $range[0].style.setProperty("--range-progress", `${progress}%`);
870    if (lastUpdated === "amount") {
871      updateDownPaymentPercentage();
872    } else {
873      updateDownPaymentAmount();
874    }
875    updatePropertyTaxAmount();
876    updateHomeownersInsuranceAmount();
877  }
878
879  let propertyTaxLastUpdated = "percentage";
880
881  function updatePropertyTaxAmount() {
882    var homeValue = parseFloat($("#home_value").val().replace(/,/g, "")) || 0;
883    var taxPercentage = parseFloat($("#property_tax_percentage").val()) || 0;
884    var taxAmount = homeValue * (taxPercentage / 100);
885    $("#property_tax_amount").val(wwd_moneyFormat(taxAmount, 0));
886  }
887
888  function updatePropertyTaxPercentage() {
889    var homeValue = parseFloat($("#home_value").val().replace(/,/g, "")) || 1;
890    var taxAmount = parseFloat($("#property_tax_amount").val().replace(/,/g, "")) || 0;
891    var percentage = (taxAmount / homeValue) * 100;
892    $("#property_tax_percentage").val(percentage.toFixed(2));
893    $('input[data-type="range-input"][data-target="property_tax_percentage"]').val(percentage);
894    updatePropertyTaxRangeProgress();
895  }
896
897  function updatePropertyTaxRangeProgress() {
898    var $range = $('input[data-type="range-input"][data-target="property_tax_percentage"]');
899    var val = parseFloat($("#property_tax_percentage").val());
900    var min = parseFloat($range.attr("min"));
901    var max = parseFloat($range.attr("max"));
902    var progress = Math.min(((val - min) / (max - min)) * 100, 100);
903    $range.val(val);
904    $range[0].style.setProperty("--range-progress", `${progress}%`);
905  }
906
907  function updateHomeownersInsuranceAmount() {
908    var homeValue = parseFloat($("#home_value").val().replace(/,/g, "")) || 0;
909    var insurancePercentage = parseFloat($("#homeowners_insurance_percentage").val()) || 0;
910    var insuranceAmount = homeValue * (insurancePercentage / 100);
911    $("#homeowners_insurance").val(wwd_moneyFormat(insuranceAmount, 0));
912  }
913
914  function updateHomeownersInsurancePercentage() {
915    var homeValue = parseFloat($("#home_value").val().replace(/,/g, "")) || 1;
916    var insuranceAmount = parseFloat($("#homeowners_insurance").val().replace(/,/g, "")) || 0;
917    var percentage = (insuranceAmount / homeValue) * 100;
918    $("#homeowners_insurance_percentage").val(percentage.toFixed(2));
919    $('input[data-type="range-input"][data-target="homeowners_insurance_percentage"]').val(percentage);
920    updateHomeownersInsuranceRangeProgress();
921  }
922
923  function updateHomeownersInsuranceRangeProgress() {
924    var $range = $('input[data-type="range-input"][data-target="homeowners_insurance_percentage"]');
925    var val = parseFloat($("#homeowners_insurance_percentage").val());
926    var min = parseFloat($range.attr("min"));
927    var max = parseFloat($range.attr("max"));
928    var progress = Math.min(((val - min) / (max - min)) * 100, 100);
929    $range.val(val);
930    $range[0].style.setProperty("--range-progress", `${progress}%`);
931  }
932
933  jQuery(document).ready(function ($) {
934    $("#down_payment_percentage").on("input", function () {
935      lastUpdated = "percentage";
936      updateDownPaymentAmount();
937      var $range = $('input[data-type="range-input"][data-target="down_payment_percentage"]');
938      var val = parseFloat($(this).val());
939      var min = parseFloat($range.attr("min"));
940      var max = parseFloat($range.attr("max"));
941
942      // Calculate and update progress
943      var progress = Math.min(((val - min) / (max - min)) * 100, 100);
944      $range.val(val);
945      $range[0].style.setProperty("--range-progress", `${progress}%`);
946    });
947
948    $("#down_payment_amount").on("input", function () {
949      lastUpdated = "amount";
950      updateDownPaymentPercentage();
951    });
952
953    $("#home_value").on("input", updateHomeValue);
954
955    $("#property_tax_percentage").on("input", function () {
956      propertyTaxLastUpdated = "percentage";
957      updatePropertyTaxAmount();
958      updatePropertyTaxRangeProgress();
959    });
960
961    $("#property_tax_amount").on("input", function () {
962      propertyTaxLastUpdated = "amount";
963      updatePropertyTaxPercentage();
964    });
965
966    $('input[data-type="range-input"][data-target="property_tax_percentage"]').on("input", function () {
967      propertyTaxLastUpdated = "percentage";
968      $("#property_tax_percentage").val($(this).val()).trigger("input");
969    });
970
971    // Initialize property tax
972    updatePropertyTaxAmount();
973
974    $("#homeowners_insurance_percentage").on("input", function () {
975      homeownersInsuranceLastUpdated = "percentage";
976      updateHomeownersInsuranceAmount();
977      updateHomeownersInsuranceRangeProgress();
978    });
979
980    $("#homeowners_insurance").on("input", function () {
981      homeownersInsuranceLastUpdated = "amount";
982      updateHomeownersInsurancePercentage();
983    });
984
985    $('input[data-type="range-input"][data-target="homeowners_insurance_percentage"]').on("input", function () {
986      homeownersInsuranceLastUpdated = "percentage";
987      $("#homeowners_insurance_percentage").val($(this).val()).trigger("input");
988    });
989
990    // Initialize homeowners insurance
991    updateHomeownersInsuranceAmount();
992
993    $('input[data-type="range-input"][data-target="home_value"]').on("input", function () {
994      $("#home_value")
995        .val(wwd_moneyFormat($(this).val(), 0))
996        .trigger("input");
997    });
998
999    $('input[data-type="range-input"][data-target="down_payment_percentage"]').on("input", function () {
1000      $("#down_payment_percentage").val($(this).val()).trigger("input");
1001    });
1002
1003    // Initialize
1004    updateDownPaymentPercentage();
1005    updateHomeValue();
1006
1007    // Function to update hidden fields based on ARM type
1008    function updateARMFields(armType) {
1009      let monthsBetweenAdjustments, monthsBeforeFirstAdjustment;
1010
1011      switch (armType) {
1012        case "10_1_arm":
1013          monthsBetweenAdjustments = 12;
1014          monthsBeforeFirstAdjustment = 120;
1015          break;
1016        case "7_1_arm":
1017          monthsBetweenAdjustments = 12;
1018          monthsBeforeFirstAdjustment = 84;
1019          break;
1020        case "5_1_arm":
1021          monthsBetweenAdjustments = 12;
1022          monthsBeforeFirstAdjustment = 60;
1023          break;
1024        case "3_1_arm":
1025          monthsBetweenAdjustments = 12;
1026          monthsBeforeFirstAdjustment = 36;
1027          break;
1028        case "10_6_month_arm":
1029          monthsBetweenAdjustments = 6;
1030          monthsBeforeFirstAdjustment = 120;
1031          break;
1032        case "7_6_month_arm":
1033          monthsBetweenAdjustments = 6;
1034          monthsBeforeFirstAdjustment = 84;
1035          break;
1036        case "5_6_month_arm":
1037          monthsBetweenAdjustments = 6;
1038          monthsBeforeFirstAdjustment = 60;
1039          break;
1040        case "3_6_month_arm":
1041          monthsBetweenAdjustments = 6;
1042          monthsBeforeFirstAdjustment = 36;
1043          break;
1044        default:
1045          monthsBetweenAdjustments = 12;
1046          monthsBeforeFirstAdjustment = 60;
1047      }
1048
1049      $("#months_between_adjustments").val(monthsBetweenAdjustments);
1050      $("#months_before_first_adjustment").val(monthsBeforeFirstAdjustment);
1051    }
1052
1053    // Listen for changes on the ARM type radio buttons
1054    $('input[name="arm_type"]').on("change", function () {
1055      updateARMFields($(this).val());
1056    });
1057
1058    // Initial update based on the default selected ARM type
1059    updateARMFields($('input[name="arm_type"]:checked').val());
1060
1061    $("#interest_rate").on("input", function () {
1062      var $range = $('input[data-type="range-input"][data-target="interest_rate"]');
1063      var val = parseFloat($(this).val());
1064      var min = parseFloat($range.attr("min"));
1065      var max = parseFloat($range.attr("max"));
1066
1067      // Calculate and update progress
1068      var progress = Math.min(((val - min) / (max - min)) * 100, 100);
1069
1070      $range.val(val);
1071      $range[0].style.setProperty("--range-progress", `${progress}%`);
1072    });
1073  });
1074
1075  // Specific event listeners for ARM mortgage calculator
1076  window.addEventListener("load", function () {
1077    window.wwd_submitForms("arm_mortgage_with_pmi_adjustment");
1078  });
1079
1080  window.addEventListener("wwdTabSwitch", handleARMMortgageWithPMIAdjustmentCalculatorSubmit);
1081
1082  function handleARMMortgageWithPMIAdjustmentCalculatorSubmit() {
1083    setTimeout(function () {
1084      window.wwd_submitForms("arm_mortgage_with_pmi_adjustment");
1085    }, 100);
1086  }
1087}
1088
1089// Initialize the ARM mortgage calculator
1090initARMMortgageWithPMIAdjustmentCalculator();

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.