`;
}
html += `
`;
html += `Year-by-Year Projection Details:
`; if(data.fundA.projection.length > 0) { html += ``;
html += ``;
if (data.fundB) { // Two-fund comparison table
html += `
`;
} else {
html += `| Year | Start Bal. (A) | Fees (A) | End Bal. (A) | Start Bal. (B) | Fees (B) | End Bal. (B) |
|---|---|---|---|---|---|---|
| ${rowA.year} | ${formatCurrency(rowA.startBalance,0,0)} | ${formatCurrency(rowA.feesPaid,0,0)} | ${formatCurrency(rowA.endBalance,0,0)} | ${formatCurrency(rowB.startBalance,0,0)} | ${formatCurrency(rowB.feesPaid,0,0)} | ${formatCurrency(rowB.endBalance,0,0)} |
| Year | Start Balance | Contribution | Gross Return | Fees Paid | End Balance | |
| ${row.year} | ${formatCurrency(row.startBalance,0,0)} | ${formatCurrency(row.contribution,0,0)} | ${formatCurrency(row.grossReturn,0,0)} | ${formatCurrency(row.feesPaid,0,0)} | ${formatCurrency(row.endBalance,0,0)} |
No projection data to display.
`; } impactAnalysisResultsDiv.innerHTML = html; } if (calculateImpactBtn) { calculateImpactBtn.addEventListener('click', calculateImpact); } // --- PDF Download --- function loadJsPdfIfNeeded(callback) { if (jsPdfLoaded) { if (callback) callback(); return; } const script = document.createElement('script'); script.src = 'https://cdnjs.cloudflare.com/ajax/libs/jspdf/2.5.1/jspdf.umd.min.js'; script.onload = () => { jsPdfLoaded = true; console.log("jsPDF loaded dynamically."); if (callback) callback(); }; script.onerror = () => { console.error("Failed to load jsPDF."); alert("Error: Could not load PDF library."); }; document.head.appendChild(script); } function downloadReportAsPdf() { if (!jsPdfLoaded) { alert("PDF library not loaded."); return; } if (!analysisDataForPdf) { alert("No analysis data to download. Please run the analysis first."); return; } const { jsPDF } = window.jspdf; const doc = new jsPDF({ unit: 'pt', format: 'a4' }); const data = analysisDataForPdf; const pageMargin = 35; const pageWidth = doc.internal.pageSize.getWidth() - 2 * pageMargin; let y = pageMargin; function addMainTitle(text) { doc.setFontSize(16); doc.setFont(undefined, 'bold'); doc.setTextColor(45, 90, 112); // Primary doc.text(text, doc.internal.pageSize.getWidth() / 2, y, { align: 'center' }); y += 30; } function addSectionTitle(text) { if (y > doc.internal.pageSize.getHeight() - 70) { doc.addPage(); y = pageMargin; } doc.setFontSize(12); doc.setFont(undefined, 'bold'); doc.setTextColor(90, 138, 158); // Secondary doc.text(text, pageMargin, y); y += 20; } function addLine(key, value, valueColor = [51,51,51]) { if (y > doc.internal.pageSize.getHeight() - 35) { doc.addPage(); y = pageMargin; } doc.setFontSize(9); doc.setFont(undefined, 'bold'); doc.setTextColor(51,51,51); doc.text(key, pageMargin, y); doc.setFont(undefined, 'normal'); doc.setTextColor(valueColor[0], valueColor[1], valueColor[2]); const valueText = String(value); doc.text(valueText, pageMargin + 200, y, { align: 'left', maxWidth: pageWidth - 200 - 5 }); y += 16; } function addInfo(text) { if (y > doc.internal.pageSize.getHeight() - 45) { doc.addPage(); y = pageMargin; } doc.setFontSize(8); doc.setFont(undefined, 'italic'); doc.setTextColor(100,100,100); const splitText = doc.splitTextToSize(text, pageWidth); doc.setFillColor(247,249,252); doc.rect(pageMargin -5, y - (doc.getTextDimensions(splitText).h / 2) - 2 , pageWidth + 10, doc.getTextDimensions(splitText).h + 8, 'F'); doc.text(splitText, pageMargin, y); y += (doc.getTextDimensions(splitText).h) + 12; } function addTable(headers, tableData, columnWidths) { if (y > doc.internal.pageSize.getHeight() - 50) { doc.addPage(); y = pageMargin; } doc.setFontSize(7); const headerFillColor = [90, 138, 158]; const headerTextColor = [255,255,255]; const rowTextColor = [51,51,51]; doc.setFillColor(headerFillColor[0], headerFillColor[1], headerFillColor[2]); doc.setTextColor(headerTextColor[0], headerTextColor[1], headerTextColor[2]); doc.setFont(undefined, 'bold'); let currentX = pageMargin; headers.forEach((header, i) => { doc.rect(currentX, y, columnWidths[i], 18, 'F'); doc.text(header, currentX + 3, y + 12); currentX += columnWidths[i]; }); y += 18; doc.setTextColor(rowTextColor[0], rowTextColor[1], rowTextColor[2]); doc.setFont(undefined, 'normal'); tableData.forEach((rowArray) => { if (y > doc.internal.pageSize.getHeight() - 30) { doc.addPage(); y = pageMargin; currentX = pageMargin; doc.setFillColor(headerFillColor[0], headerFillColor[1], headerFillColor[2]); doc.setTextColor(headerTextColor[0], headerTextColor[1], headerTextColor[2]); doc.setFont(undefined, 'bold'); headers.forEach((header, i) => { doc.rect(currentX, y, columnWidths[i], 18, 'F'); doc.text(header, currentX + 3, y + 12); currentX += columnWidths[i]; }); y += 18; doc.setTextColor(rowTextColor[0], rowTextColor[1], rowTextColor[2]); doc.setFont(undefined, 'normal'); } currentX = pageMargin; rowArray.forEach((cell, i) => { doc.rect(currentX, y, columnWidths[i], 16); const cellText = String(cell); const textLines = doc.splitTextToSize(cellText, columnWidths[i] - 6); doc.text(textLines, currentX + 3, y + 11); currentX += columnWidths[i]; }); y += 16; }); y += 8; } addMainTitle("Investment Expense Ratio Impact Report"); addInfo(`Report Generated: ${new Date().toLocaleString()}`); y += 5; addSectionTitle("Input Parameters"); addLine("Initial Investment:", formatCurrency(data.inputs.initialInvestment)); addLine("Annual Contribution:", formatCurrency(data.inputs.annualContribution)); addLine("Investment Period:", `${data.inputs.investmentPeriod} years`); addLine("Expected Gross Annual Return:", `${data.inputs.grossAnnualReturnPct.toFixed(1)}%`); addLine("Expense Ratio - Fund A:", `${data.inputs.expenseRatioAPct.toFixed(2)}%`); if (data.inputs.expenseRatioBPct !== null) { addLine("Expense Ratio - Fund B:", `${data.inputs.expenseRatioBPct.toFixed(2)}%`); } y += 10; addSectionTitle("Impact Summary"); addLine("Fund A - Final Portfolio Value:", formatCurrency(data.fundA.finalBalance)); addLine("Fund A - Total Fees Paid:", formatCurrency(data.fundA.totalFees), [217,83,79]); if (data.fundB) { y += 5; addLine("Fund B - Final Portfolio Value:", formatCurrency(data.fundB.finalBalance)); addLine("Fund B - Total Fees Paid:", formatCurrency(data.fundB.totalFees), [217,83,79]); y += 10; const valDiffColor = data.comparison.differenceInEndValue >= 0 ? [92,184,92] : [217,83,79]; const feeDiffColor = data.comparison.differenceInFees >= 0 ? [92,184,92] : [217,83,79]; addLine("Difference in Final Value (Fund B - Fund A):", formatCurrency(data.comparison.differenceInEndValue), valDiffColor); addLine("Difference in Fees Paid (Fees Saved with Fund B):", formatCurrency(data.comparison.differenceInFees), feeDiffColor); } y += 10; if (data.fundA.projection.length > 0) { addSectionTitle("Year-by-Year Projection"); let tableH, tableFData, tableCW; if (data.fundB) { tableH = ["Yr", "Start A", "Fees A", "End A", "Start B", "Fees B", "End B"]; tableCW = [30, 70, 50, 70, 70, 50, 70]; tableFData = []; for(let i=0; i < data.inputs.investmentPeriod; i++) { tableFData.push([ data.fundA.projection[i].year, formatCurrency(data.fundA.projection[i].startBalance,0,0), formatCurrency(data.fundA.projection[i].feesPaid,0,0), formatCurrency(data.fundA.projection[i].endBalance,0,0), formatCurrency(data.fundB.projection[i].startBalance,0,0), formatCurrency(data.fundB.projection[i].feesPaid,0,0), formatCurrency(data.fundB.projection[i].endBalance,0,0) ]); } } else { tableH = ["Yr", "Start Bal.", "Contrib.", "Gross Return", "Fees Paid", "End Bal."]; tableCW = [30, 80, 70, 80, 70, 80]; tableFData = data.fundA.projection.map(r => [ r.year, formatCurrency(r.startBalance,0,0), formatCurrency(r.contribution,0,0), formatCurrency(r.grossReturn,0,0), formatCurrency(r.feesPaid,0,0), formatCurrency(r.endBalance,0,0) ]); } addTable(tableH, tableFData, tableCW); } addInfo("This projection uses average rates and does not account for taxes or market volatility. It is for illustrative purposes only to demonstrate the impact of expense ratios."); const pageCount = doc.internal.getNumberOfPages(); for (let i = 1; i <= pageCount; i++) { doc.setPage(i); doc.setFontSize(7); doc.setTextColor(150); doc.text(`Page ${i} of ${pageCount} - Expense Ratio Impact Calculator`, pageMargin, doc.internal.pageSize.getHeight() - 15); } doc.save('Investment_Expense_Ratio_Impact.pdf'); } if (downloadPdfBtn) { downloadPdfBtn.addEventListener('click', () => loadJsPdfIfNeeded(downloadReportAsPdf)); } // --- Initialization --- showTab(0); if (downloadPdfBtn) downloadPdfBtn.disabled = true; if (pdfButtonContainer) pdfButtonContainer.style.display = 'block'; loadJsPdfIfNeeded(); });