Best Case (95%)
${formatCurrency(finalBest)}
Worst Case (5%)
${formatCurrency(finalWorst)}
`;
// Destroy old charts if they exist
if (assetCharts.growth) assetCharts.growth.destroy();
if (assetCharts.allocation) assetCharts.allocation.destroy();
// Create Growth Chart
const growthCtx = document.getElementById('growthChart').getContext('2d');
assetCharts.growth = new Chart(growthCtx, {
type: 'line',
data: {
labels: Array.from({length: years + 1}, (_, i) => `Year ${i}`),
datasets: [
{ label: 'Worst Case', data: results.map(r => r.worstCase), borderColor: 'rgba(239, 68, 68, 0.5)', fill: false, tension: 0.1, pointRadius: 0 },
{ label: 'Best Case', data: results.map(r => r.bestCase), borderColor: 'rgba(34, 197, 94, 0.5)', fill: '+1', backgroundColor: 'rgba(34, 197, 94, 0.1)', tension: 0.1, pointRadius: 0 },
{ label: 'Median', data: results.map(r => r.median), borderColor: '#3B82F6', fill: '-2', backgroundColor: 'rgba(239, 68, 68, 0.1)', tension: 0.1, pointRadius: 0 }
]
},
options: {
responsive: true, maintainAspectRatio: true, plugins: { title: { display: true, text: 'Portfolio Growth Projection' } },
scales: { y: { ticks: { callback: v => formatCurrency(v) } } }
}
});
// Create Allocation Chart
const allocationCtx = document.getElementById('allocationChart').getContext('2d');
assetCharts.allocation = new Chart(allocationCtx, {
type: 'doughnut',
data: {
labels: assets.map(a => a.name),
datasets: [{
data: assets.map(a => a.allocation),
backgroundColor: ['#3B82F6', '#10B981', '#F59E0B', '#8B5CF6', '#6B7280', '#EF4444', '#EC4899']
}]
},
options: {
responsive: true, maintainAspectRatio: true, plugins: { title: { display: true, text: 'Asset Allocation' } }
}
});
downloadPdfBtn.disabled = false;
};
const handlePdfDownload = () => {
const pdfContent = document.getElementById('pdf-content');
downloadPdfBtn.style.visibility = 'hidden';
html2canvas(pdfContent, { scale: 2, backgroundColor: '#ffffff' }).then(canvas => {
const imgData = canvas.toDataURL('image/png');
const { jsPDF } = window.jspdf;
const pdf = new jsPDF({ orientation: 'landscape', unit: 'pt', format: 'a4' });
const pdfWidth = pdf.internal.pageSize.getWidth();
const margin = 40;
const contentWidth = pdfWidth - margin * 2;
const canvasAspectRatio = canvas.width / canvas.height;
const contentHeight = contentWidth / canvasAspectRatio;
pdf.setFontSize(20);
pdf.text("High-Net-Worth Investment Simulation Report", margin, margin);
pdf.addImage(imgData, 'PNG', margin, margin + 20, contentWidth, contentHeight);
pdf.save('Investment-Simulation-Report.pdf');
downloadPdfBtn.style.visibility = 'visible';
});
};
// --- EVENT LISTENERS ---
window.switchTab = switchTab;
window.navigateTabs = navigateTabs;
assetTableBody.addEventListener('input', e => {
if (e.target.classList.contains('asset-input')) {
const id = parseInt(e.target.closest('tr').dataset.id);
const prop = e.target.dataset.prop;
const value = (e.target.type === 'number') ? parseFloat(e.target.value) : e.target.value;
const asset = assets.find(a => a.id === id);
if (asset) {
asset[prop] = value;
if (prop === 'allocation') updateTotalAllocation();
}
}
});
assetTableBody.addEventListener('click', e => {
if (e.target.classList.contains('remove-asset-btn')) {
const idToRemove = parseInt(e.target.closest('tr').dataset.id);
assets = assets.filter(a => a.id !== idToRemove);
render();
}
});
addAssetBtn.addEventListener('click', () => {
assets.push({ id: nextAssetId++, name: 'New Asset Class', allocation: 0, expectedReturn: 5.0, volatility: 10.0 });
render();
});
runSimulationBtn.addEventListener('click', handleRunSimulation);
downloadPdfBtn.addEventListener('click', handlePdfDownload);
// --- INITIALIZATION ---
render();
switchTab('dashboard');
updateNavButtons();
});