Worst Year's Return
${formatPercent(worstYear)}
Portfolio Growth Over Time
`;
// Render Charts
const growthChartCtx = document.getElementById('portfolioGrowthChart').getContext('2d');
if(portfolioChartInstance) portfolioChartInstance.destroy();
portfolioChartInstance = new Chart(growthChartCtx, {
type: 'line',
data: { labels, datasets: [{
label: 'Portfolio Value', data: yearlyData,
borderColor: '#2563eb', backgroundColor: 'rgba(37, 99, 235, 0.1)',
fill: true, tension: 0.3
}]},
options: { responsive: true, maintainAspectRatio: false, scales: { y: { ticks: { callback: value => formatCurrency(value) }}}}
});
const allocationChartCtx = document.getElementById('assetAllocationChart').getContext('2d');
if(allocationChartInstance) allocationChartInstance.destroy();
allocationChartInstance = new Chart(allocationChartCtx, {
type: 'doughnut',
data: {
labels: appData.portfolio.map(a => a.name),
datasets: [{ data: appData.portfolio.map(a => a.allocation),
backgroundColor: ['#3b82f6', '#10b981', '#f59e0b', '#8b5cf6', '#ef4444', '#64748b']
}]
},
options: { responsive: true, maintainAspectRatio: false, plugins: { legend: { position: 'bottom' } } }
});
document.getElementById('download-pdf-btn').addEventListener('click', generatePdf);
};
const renderPortfolioConfig = () => {
const configContent = document.getElementById('content-portfolio-config');
if (!configContent) return;
let assetsHtml = appData.portfolio.map((asset, index) => `
`).join('');
configContent.innerHTML = `
`;
updateAllocationTotal();
attachPortfolioConfigListeners();
};
const renderSimParams = () => {
const paramsContent = document.getElementById('content-simulation-params');
if (!paramsContent) return;
paramsContent.innerHTML = `
Set Simulation Parameters
`;
attachSimParamsListeners();
};
// --- EVENT LISTENERS & HANDLERS ---
const attachPortfolioConfigListeners = () => {
document.getElementById('add-asset-btn').addEventListener('click', () => {
appData.portfolio.push({ name: 'New Asset', allocation: 0, returns: { average: 5, bull: 10, bear: -5 } });
renderPortfolioConfig();
});
document.querySelectorAll('.remove-asset-btn').forEach(btn => btn.addEventListener('click', (e) => {
const index = parseInt(e.currentTarget.dataset.index);
appData.portfolio.splice(index, 1);
renderPortfolioConfig();
}));
document.getElementById('portfolio-config-form').addEventListener('input', updateAllocationTotal);
document.getElementById('save-portfolio-btn').addEventListener('click', handlePortfolioUpdate);
};
const attachSimParamsListeners = () => {
document.getElementById('simulation-years').addEventListener('input', e => {
document.getElementById('years-label').textContent = `${e.target.value} years`;
});
document.getElementById('run-simulation-btn').addEventListener('click', () => {
appData.simulationYears = parseInt(document.getElementById('simulation-years').value);
appData.marketScenario = document.querySelector('input[name="market-scenario"]:checked').value;
simulationResult = null; // Force recalculation
renderDashboard();
switchTab(0); // Switch to dashboard to see results
});
};
const updateAllocationTotal = () => {
const totalEl = document.getElementById('allocation-total');
if(!totalEl) return;
let total = 0;
document.querySelectorAll('.asset-allocation').forEach(input => {
total += parseFloat(input.value) || 0;
});
totalEl.textContent = `${total}%`;
totalEl.className = total === 100 ? 'text-green-600' : 'text-red-600';
};
const handlePortfolioUpdate = () => {
appData.initialInvestment = parseFloat(document.getElementById('initial-investment').value) || 0;
const newPortfolio = [];
document.querySelectorAll('.asset-row').forEach((row, index) => {
const name = row.querySelector('.asset-name').value;
const allocation = parseFloat(row.querySelector('.asset-allocation').value) || 0;
newPortfolio.push({ ...appData.portfolio[index], name, allocation });
});
appData.portfolio = newPortfolio;
alert('Portfolio updated!');
simulationResult = null; // Force recalculation
renderDashboard();
};
const generatePdf = () => {
loadingOverlay.style.display = 'flex';
const { jsPDF } = window.jspdf;
const pdfContent = document.getElementById('pdf-content-area');
const pdfHeader = document.getElementById('pdf-header');
document.getElementById('pdf-generated-date').textContent = new Date().toLocaleDateString('en-US', { year: 'numeric', month: 'long', day: 'numeric' });
pdfHeader.classList.remove('hidden');
html2canvas(pdfContent, { scale: 2, useCORS: true, logging: false })
.then(canvas => {
pdfHeader.classList.add('hidden');
const imgData = canvas.toDataURL('image/jpeg', 0.95);
const pdf = new jsPDF({ orientation: 'landscape', unit: 'px', format: 'a4' });
const pdfWidth = pdf.internal.pageSize.getWidth();
const pdfHeight = pdf.internal.pageSize.getHeight();
const imgProps = pdf.getImageProperties(imgData);
const imgHeight = (imgProps.height * pdfWidth) / imgProps.width;
let heightLeft = imgHeight;
let position = 0;
pdf.addImage(imgData, 'JPEG', 0, position, pdfWidth, imgHeight);
heightLeft -= pdfHeight;
while (heightLeft >= 0) {
position = heightLeft - imgHeight;
pdf.addPage();
pdf.addImage(imgData, 'JPEG', 0, position, pdfWidth, imgHeight);
heightLeft -= pdfHeight;
}
pdf.save('Portfolio-Simulation-Report.pdf');
loadingOverlay.style.display = 'none';
}).catch(err => {
console.error("PDF generation failed:", err);
pdfHeader.classList.add('hidden');
loadingOverlay.style.display = 'none';
alert('An error occurred generating the PDF.');
});
};
// --- TAB NAVIGATION & INITIALIZATION ---
const switchTab = (tabIndex) => {
activeTabIndex = tabIndex;
document.querySelectorAll('.tab-btn').forEach((btn, i) => btn.classList.toggle('active', i === tabIndex));
document.querySelectorAll('.tab-content').forEach((content, i) => content.classList.toggle('hidden', i !== tabIndex));
updateNavButtons();
};
const updateNavButtons = () => {
prevTabBtn.disabled = activeTabIndex === 0;
nextTabBtn.disabled = activeTabIndex === tabIdentifiers.length - 1;
};
const initializeUI = () => {
tabsContainer.innerHTML = '';
mainContent.innerHTML = '';
const tabs = [
{ name: 'Dashboard', id: 'dashboard' },
{ name: 'Portfolio Configuration', id: 'portfolio-config' },
{ name: 'Simulation Parameters', id: 'simulation-params' }
];
tabIdentifiers = tabs.map(t => t.id);
tabs.forEach((tab, index) => {
tabsContainer.innerHTML += `
`;
mainContent.innerHTML += `
`;
});
tabs.forEach((tab, index) => {
document.getElementById(`tab-${tab.id}`).addEventListener('click', () => switchTab(index));
});
renderDashboard();
renderPortfolioConfig();
renderSimParams();
switchTab(0);
};
initializeUI();
lucide.createIcons();
prevTabBtn.addEventListener('click', () => { if (activeTabIndex > 0) switchTab(activeTabIndex - 1); });
nextTabBtn.addEventListener('click', () => { if (activeTabIndex < tabIdentifiers.length - 1) switchTab(activeTabIndex + 1); });
});