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" : ""}"><</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" : ""}">></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.