PageSourceSearch

https://chmmortgage.com/modules/module801/calculators/js/arm_mortgage_with_pmi_adjustment.js?ver=1766021637

js chmmortgage.com collected 2026-09-29 17:57:43 UTC 44,250 bytes, 1,230 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    // Populate PDF input details
277    populatePDFInputDetails();
278  }
279
280  // Function to populate PDF input details
281  function populatePDFInputDetails() {
282    const pdfInputDetails = $container.find("#pdf-input-details-grid");
283    if (pdfInputDetails.length === 0) return;
284
285    const inputs = {
286      armType: arm_type,
287      homeValue: homeValue,
288      downPaymentAmount: downPaymentAmount,
289      downPaymentPercentage: downPaymentPercentage * 100,
290      interestRate: interestRate * 100,
291      loanTerm: loanTerm,
292      expectedAdjustment: expectedAdjustment * 100,
293      rateDirection: rateDirection,
294      maxAdjustment: maxAdjustment * 100,
295      rateFloor: rateFloor * 100,
296      includePMI: includePMI,
297      includeTaxesInsurance: includeTaxesInsurance,
298      propertyTaxPercentage: propertyTaxPercentage * 100,
299      homeownersInsurance: homeownersInsurance,
300      hoaDues: hoaDues,
301    };
302
303    // ARM type display mapping
304    const armTypeDisplayMap = {
305      "10_1_arm": "10/1 ARM",
306      "7_1_arm": "7/1 ARM",
307      "5_1_arm": "5/1 ARM",
308      "3_1_arm": "3/1 ARM",
309      "10_6_month_arm": "10/6 month ARM",
310      "7_6_month_arm": "7/6 month ARM",
311      "5_6_month_arm": "5/6 month ARM",
312      "3_6_month_arm": "3/6 month ARM",
313    };
314
315    const inputDetails = [];
316
317    // ARM Type
318    inputDetails.push({
319      label: "ARM Type",
320      value: armTypeDisplayMap[inputs.armType] || inputs.armType,
321    });
322
323    // Home Value / Purchase Price
324    inputDetails.push({
325      label: "Purchase Price",
326      value: `$${wwd_moneyFormat(inputs.homeValue, 0)}`,
327    });
328
329    // Down Payment
330    inputDetails.push({
331      label: "Down Payment",
332      value: `$${wwd_moneyFormat(inputs.downPaymentAmount, 0)} (${inputs.downPaymentPercentage.toFixed(1)}%)`,
333    });
334
335    // Initial Interest Rate
336    inputDetails.push({
337      label: "Initial Interest Rate",
338      value: `${inputs.interestRate.toFixed(3)}%`,
339    });
340
341    // Loan Term
342    inputDetails.push({
343      label: "Loan Term",
344      value: `${inputs.loanTerm} years`,
345    });
346
347    // Expected Adjustment
348    inputDetails.push({
349      label: "Expected Adjustment",
350      value: `${inputs.expectedAdjustment.toFixed(2)}%`,
351    });
352
353    // Rate Direction
354    inputDetails.push({
355      label: "Rate Direction",
356      value: inputs.rateDirection === "up" ? "Increasing" : "Decreasing",
357    });
358
359    // Max Adjustment
360    inputDetails.push({
361      label: "Max Adjustment",
362      value: `${inputs.maxAdjustment.toFixed(2)}%`,
363    });
364
365    // Rate Floor
366    inputDetails.push({
367      label: "Rate Floor",
368      value: `${inputs.rateFloor.toFixed(2)}%`,
369    });
370
371    // Include PMI
372    inputDetails.push({
373      label: "Include PMI",
374      value: inputs.includePMI ? "Yes" : "No",
375    });
376
377    // Include Taxes & Insurance
378    inputDetails.push({
379      label: "Include Taxes & Insurance",
380      value: inputs.includeTaxesInsurance ? "Yes" : "No",
381    });
382
383    // Property Tax
384    inputDetails.push({
385      label: "Property Tax",
386      value: `${inputs.propertyTaxPercentage.toFixed(2)}%`,
387    });
388
389    // Homeowners Insurance
390    inputDetails.push({
391      label: "Homeowners Insurance",
392      value: `$${wwd_moneyFormat(inputs.homeownersInsurance, 0)}`,
393    });
394
395    // HOA Dues
396    inputDetails.push({
397      label: "HOA Dues",
398      value: `$${wwd_moneyFormat(inputs.hoaDues, 0)}`,
399    });
400
401    let html = "";
402    inputDetails.forEach((detail) => {
403      if (detail.value !== null) {
404        html += `
405          <div>
406            <span class="block text-sm text-gray-600 mb-1">${detail.label}</span>
407            <span class="block text-lg font-bold">${detail.value}</span>
408          </div>
409        `;
410      }
411    });
412
413    pdfInputDetails.html(html);
414    console.log("PDF Input Details: populated successfully");
415  }
416
417  function updatePaymentBreakdownChart(result, $container, chartId) {
418    console.log("Updating chart with ID:", chartId);
419    console.log("Existing charts:", window.paymentBreakdownCharts);
420
421    // Destroy existing chart if it exists
422    if (window.paymentBreakdownCharts && window.paymentBreakdownCharts[chartId]) {
423      console.log("Attempting to destroy chart:", chartId);
424      try {
425        window.paymentBreakdownCharts[chartId].destroy();
426        console.log("Chart destroyed successfully");
427      } catch (error) {
428        console.error("Error destroying chart:", error);
429      }
430      delete window.paymentBreakdownCharts[chartId];
431    } else {
432      console.log("No existing chart found to destroy");
433    }
434
435    const amortizationSchedule = result.amortizationSchedule;
436
437    console.log(amortizationSchedule);
438
439    function updateChartData(monthIndex) {
440      console.log("Updating chart data for month:", monthIndex);
441      const scheduleItem = amortizationSchedule[monthIndex];
442
443      if (!scheduleItem) {
444        console.error("No schedule item found for month:", monthIndex);
445        return;
446      }
447
448      // Parse the values, removing currency symbols and commas
449      const interestRate = parseFloat(scheduleItem.interestRate);
450      const principal = parseFloat(scheduleItem.principalPayment);
451      const interest = parseFloat(scheduleItem.interestPayment);
452      const pmi = parseFloat(scheduleItem.monthlyPMI);
453      const taxesAndHoa = parseFloat(scheduleItem.monthlyTaxesHoa);
454      const homeownersInsurance = parseFloat(scheduleItem.monthlyHomeownersInsurance);
455
456      // Update the chart data
457      chart.data.datasets[0].data = [principal, interest, pmi, taxesAndHoa, homeownersInsurance];
458
459      const totalMonthlyPayment = principal + interest + pmi + taxesAndHoa + homeownersInsurance;
460      // Update the centerTextPlugin options
461      chart.options.plugins.centerText = {
462        text: wwd_moneyFormat(totalMonthlyPayment, 0),
463      };
464
465      chart.update();
466
467      // Update output elements
468      const currentDate = new Date();
469      const futureDate = new Date(currentDate.getFullYear(), currentDate.getMonth() + monthIndex, 1);
470      const year = Math.floor(monthIndex / 12) + 1;
471      const month = (monthIndex % 12) + 1;
472
473      $(".current-interest-rate").text(interestRate.toFixed(3) + "%");
474      $(".out-current-year").text(year);
475      $(".out-current-month").text(month);
476      $(".current-year-savings").text("$" + wwd_moneyFormat(totalMonthlyPayment, 0));
477      $(".out-current-month-year").text(futureDate.toLocaleString("default", { month: "long", year: "numeric" }));
478
479      // Update legend output
480      $("#out-monthly-principal").text(wwd_moneyFormat(principal, 0));
481      $("#out-monthly-interest").text(wwd_moneyFormat(interest, 0));
482      $("#out-pmi").text(wwd_moneyFormat(pmi, 0));
483      $("#out-taxes-hoa").text(wwd_moneyFormat(taxesAndHoa, 0));
484      $("#out-homeowners-insurance").text(wwd_moneyFormat(homeownersInsurance, 0));
485
486      // Show/hide PMI row based on whether PMI is included
487      if (pmi > 0) {
488        $(".pmi-out-wrapper").show();
489      } else {
490        $(".pmi-out-wrapper").hide();
491      }
492
493      // Show/hide taxes and insurance rows based on whether they are included
494      if (taxesAndHoa > 0 || homeownersInsurance > 0) {
495        $(".taxes-hoa-out-wrapper").show();
496        $(".insurance-out-wrapper").show();
497      } else {
498        $(".taxes-hoa-out-wrapper").hide();
499        $(".insurance-out-wrapper").hide();
500      }
501
502      console.log("Chart data updated successfully");
503    }
504
505    // Create the data for the chart
506    const data = {
507      labels: ["Principal", "Interest", "PMI", "Taxes and HOA", "Homeowners Insurance"],
508      datasets: [
509        {
510          data: [0, 0, 0, 0, 0],
511          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],
512          hoverOffset: 20,
513        },
514      ],
515    };
516
517    // Custom plugin for center text
518    const centerTextPlugin = {
519      id: "centerText",
520      options: {
521        text: "0",
522      },
523      afterDraw: (chart, args, options) => {
524        const width = chart.width;
525        const height = chart.height;
526        const ctx = chart.ctx;
527        ctx.restore();
528
529        // Title text
530        const titleFontSize = (height / 360).toFixed(2);
531        ctx.font = `${titleFontSize}em "Plus Jakarta Sans"`;
532        ctx.fillStyle = "#000";
533        ctx.textBaseline = "middle";
534        ctx.textAlign = "center";
535        ctx.fillText("Your Estimated", width / 2, height / 2.3 - height / 12);
536        ctx.fillText("Monthly Payment:", width / 2, height / 2.24 - height / 24);
537
538        // Amount text
539        const amountFontSize = (height / 110).toFixed(2);
540        ctx.font = `bold ${amountFontSize}em "Plus Jakarta Sans"`;
541        ctx.fillStyle = "#555";
542
543        // Render dollar sign
544        const dollarSignFontSize = (height / 240).toFixed(2);
545        ctx.font = `${dollarSignFontSize}em "Plus Jakarta Sans"`;
546        ctx.fillText("$", width / 2.45 - ctx.measureText(result.totalMonthlyPayment).width / 2 - ctx.measureText("$").width / 2, height / 2.1 + height / 24);
547
548        // Render amount
549        ctx.font = `bold ${amountFontSize}em "Plus Jakarta Sans"`;
550        ctx.fontFamily = "Plus Jakarta Sans";
551        ctx.fillStyle = "#000";
552        const text = options.text;
553        // const text = result.totalMonthlyPayment.replace(/^\$/, "").trim();
554        ctx.fillText(text, width / 2.15 + ctx.measureText("$").width / 2, height / 2 + height / 24);
555
556        // ctx.save();
557        ctx.restore();
558      },
559    };
560
561    // Chart configuration
562    const config = {
563      type: "doughnut",
564      data: data,
565      options: {
566        responsive: true,
567        plugins: {
568          legend: {
569            display: false,
570          },
571          title: {
572            display: false,
573          },
574          tooltip: {
575            callbacks: {
576              label: function (context) {
577                let label = context.label || "";
578                if (label) {
579                  label += ": ";
580                }
581                if (context.parsed !== null) {
582                  label += "$" + window.wwd_moneyFormat(context.parsed, 0);
583                }
584                return label;
585              },
586            },
587          },
588        },
589        cutout: "70%",
590        onHover: (event, chartElement) => {
591          event.native.target.style.cursor = chartElement[0] ? "pointer" : "default";
592        },
593        layout: {
594          padding: 20,
595        },
596      },
597      plugins: [centerTextPlugin],
598    };
599
600    // Create the chart
601    if (!window.paymentBreakdownCharts) {
602      window.paymentBreakdownCharts = {};
603    }
604    const chart = new Chart($container[0], config);
605    window.paymentBreakdownCharts[chartId] = chart;
606    console.log("New chart created with ID:", chartId);
607
608    // Initialize the chart with the first month's data
609    updateChartData(0);
610
611    // Remove any existing event listener
612    $("#yearSlider").off("input.updateChartData");
613    $("#yearSlider").val(0);
614
615    // Add event listener for the year slider with a namespace
616    $("#yearSlider").on("input.updateChartData", function () {
617      const selectedMonth = parseInt($(this).val());
618      updateChartData(selectedMonth - 1);
619    });
620
621    // Store the updateChartData function on the chart object
622    chart.updateChartData = updateChartData;
623  }
624
625  function updateAmortizationChart(result, $container) {
626    // Destroy existing chart if it exists
627    if (window.amortizationChart) {
628      window.amortizationChart.destroy();
629    }
630
631    const schedule = result.amortizationSchedule;
632    const labels = schedule.map((entry) => entry.month);
633    const principalData = schedule.map((entry) => entry.totalPrincipalPaid);
634    const interestData = schedule.map((entry) => entry.totalInterestPaid);
635    const remainingBalanceData = schedule.map((entry) => entry.remainingBalance);
636
637    const principalColor = WWD_Calculators.colors.principal_color;
638    const interestColor = WWD_Calculators.colors.interest_color;
639    const remainingBalanceColor = WWD_Calculators.colors.remaining_balance_color;
640
641    const data = {
642      labels: labels,
643      datasets: [
644        {
645          label: "Principal",
646          data: principalData,
647          borderColor: principalColor,
648          backgroundColor: principalColor,
649          fill: false,
650          tension: 0.1,
651        },
652        {
653          label: "Interest",
654          data: interestData,
655          borderColor: interestColor,
656          backgroundColor: interestColor,
657          fill: false,
658          tension: 0.1,
659        },
660        {
661          label: "Remaining",
662          data: remainingBalanceData,
663          borderColor: remainingBalanceColor,
664          backgroundColor: remainingBalanceColor,
665          fill: false,
666          tension: 0.1,
667        },
668      ],
669    };
670
671    const config = {
672      type: "line",
673      data: data,
674      options: {
675        responsive: true,
676        plugins: {
677          legend: {
678            position: "top",
679          },
680          title: {
681            display: false,
682            text: "Amortization Schedule",
683          },
684          tooltip: {
685            mode: "index",
686            intersect: false,
687            callbacks: {
688              title: function (context) {
689                return "Month " + context[0].label;
690              },
691              label: function (context) {
692                let label = context.dataset.label || "";
693                if (label) {
694                  label += ": ";
695                }
696                if (context.parsed.y !== null) {
697                  label += new Intl.NumberFormat("en-US", { style: "currency", currency: "USD" }).format(context.parsed.y);
698                }
699                return label;
700              },
701            },
702          },
703        },
704        scales: {
705          x: {
706            title: {
707              display: true,
708              text: "Months",
709            },
710            ticks: {
711              callback: function (value, index, values) {
712                return value % 100 === 0 ? value + " mo" : "";
713              },
714            },
715          },
716          y: {
717            title: {
718              display: true,
719              text: "Amount",
720            },
721            ticks: {
722              callback: function (value, index, values) {
723                return "$" + value.toLocaleString();
724              },
725            },
726          },
727        },
728        elements: {
729          line: {
730            borderWidth: 1.5, // Make lines thinner
731          },
732          point: {
733            radius: 0,
734            hitRadius: 5,
735            hoverRadius: 3,
736          },
737        },
738      },
739    };
740
741    window.amortizationChart = new Chart($container[0], config);
742  }
743
744  function updatePrincipalVsInterestChart(result, $container) {
745    // Destroy existing chart if it exists
746    if (window.principalVsInterestChart) {
747      window.principalVsInterestChart.destroy();
748    }
749
750    const schedule = result.amortizationSchedule;
751    const labels = schedule.map((entry) => entry.month);
752    const principalData = schedule.map((entry) => entry.principalPayment);
753    const interestData = schedule.map((entry) => entry.interestPayment);
754
755    const principalColor = WWD_Calculators.colors.principal_color;
756    const interestColor = WWD_Calculators.colors.interest_color;
757
758    const data = {
759      labels: labels,
760      datasets: [
761        {
762          label: "Principal",
763          data: principalData,
764          backgroundColor: principalColor,
765          fill: true,
766        },
767        {
768          label: "Interest",
769          data: interestData,
770          backgroundColor: interestColor,
771          fill: true,
772        },
773      ],
774    };
775
776    const config = {
777      type: "line",
778      data: data,
779      options: {
780        responsive: true,
781        plugins: {
782          legend: {
783            position: "top",
784          },
785          title: {
786            display: false,
787            text: "Principal vs Interest",
788          },
789          tooltip: {
790            mode: "index",
791            intersect: false,
792            callbacks: {
793              title: function (context) {
794                return "Month " + context[0].label;
795              },
796              label: function (context) {
797                let label = context.dataset.label || "";
798                if (label) {
799                  label += ": ";
800                }
801                if (context.parsed.y !== null) {
802                  label += new Intl.NumberFormat("en-US", { style: "currency", currency: "USD" }).format(context.parsed.y);
803                }
804                return label;
805              },
806            },
807          },
808        },
809        scales: {
810          x: {
811            stacked: true,
812            title: {
813              display: true,
814              text: "Months",
815            },
816            ticks: {
817              callback: function (value, index, values) {
818                return value % 100 === 0 ? value + "mo" : "";
819              },
820            },
821          },
822          y: {
823            stacked: true,
824            title: {
825              display: true,
826              text: "Amount",
827            },
828            ticks: {
829              callback: function (value, index, values) {
830                return "$" + value.toLocaleString();
831              },
832            },
833          },
834        },
835        elements: {
836          line: {
837            tension: 0.4,
838          },
839          point: {
840            radius: 0,
841          },
842        },
843      },
844    };
845
846    window.principalVsInterestChart = new Chart($container[0], config);
847  }
848
849  function updateAmortizationSchedule(result, $container) {
850    const schedule = result.amortizationSchedule;
851    const itemsPerPage = 12;
852    let currentPage = 1;
853
854    function renderTable(page) {
855      const startIndex = (page - 1) * itemsPerPage;
856      const endIndex = startIndex + itemsPerPage;
857      const pageItems = schedule.slice(startIndex, endIndex);
858
859      let tableHtml = `
860      <div class="overflow-x-auto w-full wwd-amortization-table">
861        <table class="w-full text-sm text-left">
862          <thead>
863            <tr class="bg-gray-100">
864              <th class="px-4 py-2 text-[var(--wwd-heading-color)]">Month</th>
865              <th class="px-4 py-2 text-[var(--wwd-heading-color)]">Interest</th>
866              <th class="px-4 py-2 text-[var(--wwd-heading-color)]">Principal</th>
867              <th class="px-4 py-2 text-[var(--wwd-heading-color)]">Remaining</th>
868            </tr>
869          </thead>
870          <tbody>
871    `;
872
873      pageItems.forEach((item, index) => {
874        const rowClass = index % 2 === 0 ? "bg-white" : "bg-gray-50";
875        tableHtml += `
876        <tr class="${rowClass}">
877          <td class="px-4 py-2 text-[var(--wwd-body-color)]">${item.month}</td>
878          <td class="px-4 py-2 text-[var(--wwd-body-color)]">$${wwd_moneyFormat(item.interestPayment, 0)}</td>
879          <td class="px-4 py-2 text-[var(--wwd-body-color)]">$${wwd_moneyFormat(item.principalPayment, 0)}</td>
880          <td class="px-4 py-2 text-[var(--wwd-body-color)]">$${wwd_moneyFormat(item.remainingBalance, 0)}</td>
881        </tr>
882      `;
883      });
884
885      tableHtml += `
886          </tbody>
887        </table>
888      </div>
889    `;
890
891      $container.html(tableHtml);
892
893      // Add pagination
894      const totalPages = Math.ceil(schedule.length / itemsPerPage);
895      let paginationHtml = `
896        <div class="flex items-center justify-center mt-4 space-x-2">
897          <button class="pagination-prev px-3 py-1 rounded border ${page === 1 ? "opacity-50 cursor-not-allowed" : ""}">&lt;</button>
898      `;
899
900      for (let i = 1; i <= totalPages; i++) {
901        if (i === 1 || i === totalPages || (i >= page - 2 && i <= page + 2)) {
902          paginationHtml += `
903            <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>
904          `;
905        } else if (i === page - 3 || i === page + 3) {
906          paginationHtml += `<span class="px-1">...</span>`;
907        }
908      }
909
910      paginationHtml += `
911          <button class="pagination-next px-3 py-1 rounded border text-xs sm:text-sm ${page === totalPages ? "opacity-50 cursor-not-allowed" : ""}">&gt;</button>
912        </div>
913      `;
914
915      $container.append(paginationHtml);
916
917      // Add event listeners for pagination
918      $container.find(".pagination-prev").on("click", function () {
919        if (currentPage > 1) {
920          currentPage--;
921          renderTable(currentPage);
922        }
923      });
924
925      $container.find(".pagination-next").on("click", function () {
926        if (currentPage < totalPages) {
927          currentPage++;
928          renderTable(currentPage);
929        }
930      });
931
932      $container.find(".pagination-page").on("click", function () {
933        currentPage = parseInt(jQuery(this).text());
934        renderTable(currentPage);
935      });
936    }
937
938    renderTable(currentPage);
939  }
940};
941
942function initARMMortgageWithPMIAdjustmentCalculator() {
943  var hasARMMortgageWithPMIAdjustmentCalculator = jQuery(".wwd-calculator-form[data-type='arm_mortgage_with_pmi_adjustment']").length > 0;
944
945  if (!hasARMMortgageWithPMIAdjustmentCalculator) {
946    return;
947  }
948
949  jQuery(".wwd-calculator-wrapper.arm_mortgage_with_pmi_adjustment-calculator").each(function () {
950    $ = jQuery;
951    const $wrapper = $(this);
952    const $interestRateInput = $wrapper.find('input[name="interest_rate"]');
953    const $creditScoreInputs = $wrapper.find('input[name="credit_score"]');
954
955    $creditScoreInputs.on("change", function () {
956      const selectedCreditScore = $(this).val();
957      updateInterestRate(selectedCreditScore);
958    });
959
960    function updateInterestRate(creditScore) {
961      if (WWD_Calculators && WWD_Calculators.credit_score_rates) {
962        const rateKey = `arm_mortgage_credit_score_${creditScore}`;
963        const newRate = WWD_Calculators.credit_score_rates[rateKey];
964
965        if (newRate) {
966          $interestRateInput.val(newRate);
967          $interestRateInput.trigger("change");
968        }
969      }
970    }
971
972    // Initialize interest rate based on default credit score
973    const defaultCreditScore = $creditScoreInputs.filter(":checked").val();
974    updateInterestRate(defaultCreditScore);
975  });
976
977  let lastUpdated = "amount";
978
979  function updateDownPaymentAmount() {
980    var homeValue = parseFloat($("#home_value").val().replace(/,/g, "")) || 0;
981    var percentage = parseFloat($("#down_payment_percentage").val()) || 0;
982    var amount = homeValue * (percentage / 100);
983    $("#down_payment_amount").val(wwd_moneyFormat(amount, 0));
984  }
985
986  function updateDownPaymentPercentage() {
987    var homeValue = parseFloat($("#home_value").val().replace(/,/g, "")) || 1;
988    var amount = parseFloat($("#down_payment_amount").val().replace(/,/g, "")) || 0;
989    var percentage = (amount / homeValue) * 100;
990    $("#down_payment_percentage").val(Math.min(percentage, 100).toFixed(2));
991    $('input[data-type="range-input"][data-target="down_payment_percentage"]').val(Math.min(percentage, 80));
992    // update progress
993    var $range = $('input[data-type="range-input"][data-target="down_payment_percentage"]');
994    var val = parseFloat($("#down_payment_percentage").val());
995    var min = parseFloat($range.attr("min"));
996    var max = parseFloat($range.attr("max"));
997    var progress = Math.min(((val - min) / (max - min)) * 100, 100);
998    $range.val(val);
999    $range[0].style.setProperty("--range-progress", `${progress}%`);
1000  }
1001
1002  function updateHomeValue() {
1003    var homeValue = parseFloat($("#home_value").val().replace(/,/g, "")) || 0;
1004    const $range = $('input[data-type="range-input"][data-target="home_value"]');
1005    var min = parseFloat($range.attr("min"));
1006    var max = parseFloat($range.attr("max"));
1007    var progress = Math.min(((homeValue - min) / (max - min)) * 100, 100);
1008    $range.val(homeValue);
1009    $range[0].style.setProperty("--range-progress", `${progress}%`);
1010    if (lastUpdated === "amount") {
1011      updateDownPaymentPercentage();
1012    } else {
1013      updateDownPaymentAmount();
1014    }
1015    updatePropertyTaxAmount();
1016    updateHomeownersInsuranceAmount();
1017  }
1018
1019  let propertyTaxLastUpdated = "percentage";
1020
1021  function updatePropertyTaxAmount() {
1022    var homeValue = parseFloat($("#home_value").val().replace(/,/g, "")) || 0;
1023    var taxPercentage = parseFloat($("#property_tax_percentage").val()) || 0;
1024    var taxAmount = homeValue * (taxPercentage / 100);
1025    $("#property_tax_amount").val(wwd_moneyFormat(taxAmount, 0));
1026  }
1027
1028  function updatePropertyTaxPercentage() {
1029    var homeValue = parseFloat($("#home_value").val().replace(/,/g, "")) || 1;
1030    var taxAmount = parseFloat($("#property_tax_amount").val().replace(/,/g, "")) || 0;
1031    var percentage = (taxAmount / homeValue) * 100;
1032    $("#property_tax_percentage").val(percentage.toFixed(2));
1033    $('input[data-type="range-input"][data-target="property_tax_percentage"]').val(percentage);
1034    updatePropertyTaxRangeProgress();
1035  }
1036
1037  function updatePropertyTaxRangeProgress() {
1038    var $range = $('input[data-type="range-input"][data-target="property_tax_percentage"]');
1039    var val = parseFloat($("#property_tax_percentage").val());
1040    var min = parseFloat($range.attr("min"));
1041    var max = parseFloat($range.attr("max"));
1042    var progress = Math.min(((val - min) / (max - min)) * 100, 100);
1043    $range.val(val);
1044    $range[0].style.setProperty("--range-progress", `${progress}%`);
1045  }
1046
1047  function updateHomeownersInsuranceAmount() {
1048    var homeValue = parseFloat($("#home_value").val().replace(/,/g, "")) || 0;
1049    var insurancePercentage = parseFloat($("#homeowners_insurance_percentage").val()) || 0;
1050    var insuranceAmount = homeValue * (insurancePercentage / 100);
1051    $("#homeowners_insurance").val(wwd_moneyFormat(insuranceAmount, 0));
1052  }
1053
1054  function updateHomeownersInsurancePercentage() {
1055    var homeValue = parseFloat($("#home_value").val().replace(/,/g, "")) || 1;
1056    var insuranceAmount = parseFloat($("#homeowners_insurance").val().replace(/,/g, "")) || 0;
1057    var percentage = (insuranceAmount / homeValue) * 100;
1058    $("#homeowners_insurance_percentage").val(percentage.toFixed(2));
1059    $('input[data-type="range-input"][data-target="homeowners_insurance_percentage"]').val(percentage);
1060    updateHomeownersInsuranceRangeProgress();
1061  }
1062
1063  function updateHomeownersInsuranceRangeProgress() {
1064    var $range = $('input[data-type="range-input"][data-target="homeowners_insurance_percentage"]');
1065    var val = parseFloat($("#homeowners_insurance_percentage").val());
1066    var min = parseFloat($range.attr("min"));
1067    var max = parseFloat($range.attr("max"));
1068    var progress = Math.min(((val - min) / (max - min)) * 100, 100);
1069    $range.val(val);
1070    $range[0].style.setProperty("--range-progress", `${progress}%`);
1071  }
1072
1073  jQuery(document).ready(function ($) {
1074    $("#down_payment_percentage").on("input", function () {
1075      lastUpdated = "percentage";
1076      updateDownPaymentAmount();
1077      var $range = $('input[data-type="range-input"][data-target="down_payment_percentage"]');
1078      var val = parseFloat($(this).val());
1079      var min = parseFloat($range.attr("min"));
1080      var max = parseFloat($range.attr("max"));
1081
1082      // Calculate and update progress
1083      var progress = Math.min(((val - min) / (max - min)) * 100, 100);
1084      $range.val(val);
1085      $range[0].style.setProperty("--range-progress", `${progress}%`);
1086    });
1087
1088    $("#down_payment_amount").on("input", function () {
1089      lastUpdated = "amount";
1090      updateDownPaymentPercentage();
1091    });
1092
1093    $("#home_value").on("input", updateHomeValue);
1094
1095    $("#property_tax_percentage").on("input", function () {
1096      propertyTaxLastUpdated = "percentage";
1097      updatePropertyTaxAmount();
1098      updatePropertyTaxRangeProgress();
1099    });
1100
1101    $("#property_tax_amount").on("input", function () {
1102      propertyTaxLastUpdated = "amount";
1103      updatePropertyTaxPercentage();
1104    });
1105
1106    $('input[data-type="range-input"][data-target="property_tax_percentage"]').on("input", function () {
1107      propertyTaxLastUpdated = "percentage";
1108      $("#property_tax_percentage").val($(this).val()).trigger("input");
1109    });
1110
1111    // Initialize property tax
1112    updatePropertyTaxAmount();
1113
1114    $("#homeowners_insurance_percentage").on("input", function () {
1115      homeownersInsuranceLastUpdated = "percentage";
1116      updateHomeownersInsuranceAmount();
1117      updateHomeownersInsuranceRangeProgress();
1118    });
1119
1120    $("#homeowners_insurance").on("input", function () {
1121      homeownersInsuranceLastUpdated = "amount";
1122      updateHomeownersInsurancePercentage();
1123    });
1124
1125    $('input[data-type="range-input"][data-target="homeowners_insurance_percentage"]').on("input", function () {
1126      homeownersInsuranceLastUpdated = "percentage";
1127      $("#homeowners_insurance_percentage").val($(this).val()).trigger("input");
1128    });
1129
1130    // Initialize homeowners insurance
1131    updateHomeownersInsuranceAmount();
1132
1133    $('input[data-type="range-input"][data-target="home_value"]').on("input", function () {
1134      $("#home_value")
1135        .val(wwd_moneyFormat($(this).val(), 0))
1136        .trigger("input");
1137    });
1138
1139    $('input[data-type="range-input"][data-target="down_payment_percentage"]').on("input", function () {
1140      $("#down_payment_percentage").val($(this).val()).trigger("input");
1141    });
1142
1143    // Initialize
1144    updateDownPaymentPercentage();
1145    updateHomeValue();
1146
1147    // Function to update hidden fields based on ARM type
1148    function updateARMFields(armType) {
1149      let monthsBetweenAdjustments, monthsBeforeFirstAdjustment;
1150
1151      switch (armType) {
1152        case "10_1_arm":
1153          monthsBetweenAdjustments = 12;
1154          monthsBeforeFirstAdjustment = 120;
1155          break;
1156        case "7_1_arm":
1157          monthsBetweenAdjustments = 12;
1158          monthsBeforeFirstAdjustment = 84;
1159          break;
1160        case "5_1_arm":
1161          monthsBetweenAdjustments = 12;
1162          monthsBeforeFirstAdjustment = 60;
1163          break;
1164        case "3_1_arm":
1165          monthsBetweenAdjustments = 12;
1166          monthsBeforeFirstAdjustment = 36;
1167          break;
1168        case "10_6_month_arm":
1169          monthsBetweenAdjustments = 6;
1170          monthsBeforeFirstAdjustment = 120;
1171          break;
1172        case "7_6_month_arm":
1173          monthsBetweenAdjustments = 6;
1174          monthsBeforeFirstAdjustment = 84;
1175          break;
1176        case "5_6_month_arm":
1177          monthsBetweenAdjustments = 6;
1178          monthsBeforeFirstAdjustment = 60;
1179          break;
1180        case "3_6_month_arm":
1181          monthsBetweenAdjustments = 6;
1182          monthsBeforeFirstAdjustment = 36;
1183          break;
1184        default:
1185          monthsBetweenAdjustments = 12;
1186          monthsBeforeFirstAdjustment = 60;
1187      }
1188
1189      $("#months_between_adjustments").val(monthsBetweenAdjustments);
1190      $("#months_before_first_adjustment").val(monthsBeforeFirstAdjustment);
1191    }
1192
1193    // Listen for changes on the ARM type radio buttons
1194    $('input[name="arm_type"]').on("change", function () {
1195      updateARMFields($(this).val());
1196    });
1197
1198    // Initial update based on the default selected ARM type
1199    updateARMFields($('input[name="arm_type"]:checked').val());
1200
1201    $("#interest_rate").on("input", function () {
1202      var $range = $('input[data-type="range-input"][data-target="interest_rate"]');
1203      var val = parseFloat($(this).val());
1204      var min = parseFloat($range.attr("min"));
1205      var max = parseFloat($range.attr("max"));
1206
1207      // Calculate and update progress
1208      var progress = Math.min(((val - min) / (max - min)) * 100, 100);
1209
1210      $range.val(val);
1211      $range[0].style.setProperty("--range-progress", `${progress}%`);
1212    });
1213  });
1214
1215  // Specific event listeners for ARM mortgage calculator
1216  window.addEventListener("load", function () {
1217    window.wwd_submitForms("arm_mortgage_with_pmi_adjustment");
1218  });
1219
1220  window.addEventListener("wwdTabSwitch", handleARMMortgageWithPMIAdjustmentCalculatorSubmit);
1221
1222  function handleARMMortgageWithPMIAdjustmentCalculatorSubmit() {
1223    setTimeout(function () {
1224      window.wwd_submitForms("arm_mortgage_with_pmi_adjustment");
1225    }, 100);
1226  }
1227}
1228
1229// Initialize the ARM mortgage calculator
1230initARMMortgageWithPMIAdjustmentCalculator();

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.