Sustainable Portfolio Optimizer
Define Your Investment & Sustainability Preferences
The total capital you wish to allocate.
How much volatility are you comfortable with?
How strong is your focus on sustainability?
What is the primary objective of optimization?
Select Available Assets (Simulated)
Choose the simulated assets you wish to include in your portfolio. You can select multiple assets.
Loading assets...
ESG Score: 0 (Poor) to 100 (Excellent). Expected Return: Annual percentage. Volatility: Annual percentage (standard deviation).
Optimized Portfolio Summary
Define your preferences and click "Optimize Portfolio" to see results.
Portfolio Visualizations
Portfolio Allocation
Optimized Portfolio Metrics
Optimize your portfolio to generate charts.
Disclaimer: This optimization uses simplified models and simulated data. Actual investment performance, risk, and ESG outcomes can vary significantly. This tool is for illustrative purposes only and should not be used for real investment decisions.
`; resultsOutput.innerHTML = allocationTableHtml; } /** * Renders the portfolio allocation and metrics charts. * @param {Object} optimizedPortfolio - The calculated optimized portfolio. */ function renderCharts(optimizedPortfolio) { if (!optimizedPortfolio || !optimizedPortfolio.allocations || optimizedPortfolio.allocations.length === 0) { chartPlaceholder.classList.remove('hidden'); if (allocationChartInstance) allocationChartInstance.destroy(); if (metricsChartInstance) metricsChartInstance.destroy(); return; } chartPlaceholder.classList.add('hidden'); // Hide placeholder if charts can be rendered // --- Allocation Chart (Pie Chart) --- const allocCtx = document.getElementById('allocationChart').getContext('2d'); if (allocationChartInstance) { allocationChartInstance.destroy(); } const allocationLabels = optimizedPortfolio.allocations.filter(a => a.weight > 0.001).map(a => a.asset.name); const allocationData = optimizedPortfolio.allocations.filter(a => a.weight > 0.001).map(a => (a.weight * 100).toFixed(2)); const allocationColors = allocationLabels.map((_, i) => `hsl(${i * (360 / allocationLabels.length)}, 70%, 70%)`); const allocationBorderColors = allocationLabels.map((_, i) => `hsl(${i * (360 / allocationLabels.length)}, 70%, 50%)`); allocationChartInstance = new Chart(allocCtx, { type: 'pie', data: { labels: allocationLabels, datasets: [{ label: 'Allocation (%)', data: allocationData, backgroundColor: allocationColors, borderColor: allocationBorderColors, borderWidth: 1 }] }, options: { responsive: true, maintainAspectRatio: false, plugins: { legend: { position: 'right', labels: { font: { size: 12, family: 'Inter' }, color: '#333' } }, title: { display: false // Title is already in h3 }, tooltip: { callbacks: { label: function(context) { let label = context.label || ''; if (label) { label += ': '; } label += `${context.parsed.toFixed(2)}%`; return label; } } } } } }); // --- Metrics Chart (Bar Chart) --- const metricsCtx = document.getElementById('metricsChart').getContext('2d'); if (metricsChartInstance) { metricsChartInstance.destroy(); } metricsChartInstance = new Chart(metricsCtx, { type: 'bar', data: { labels: ['Expected Return', 'Volatility', 'ESG Score'], datasets: [{ label: 'Portfolio Metrics', data: [ optimizedPortfolio.expectedReturn, optimizedPortfolio.volatility, optimizedPortfolio.esgScore ], backgroundColor: ['#34D399', '#FCA5A5', '#60A5FA'], // Green, Red, Blue borderColor: ['#10B981', '#EF4444', '#3B82F6'], borderWidth: 1 }] }, options: { responsive: true, maintainAspectRatio: false, plugins: { legend: { display: false }, title: { display: false // Title is already in h3 }, tooltip: { callbacks: { label: function(context) { let label = context.label || ''; if (label) { label += ': '; } if (context.label === 'ESG Score') { label += `${context.parsed.y.toFixed(1)} / 100`; } else { label += `${context.parsed.y.toFixed(2)}%`; } return label; } } } }, scales: { y: { beginAtZero: true, title: { display: true, text: 'Value (%) / Score (0-100)', font: { size: 14, family: 'Inter' }, color: '#333' }, ticks: { font: { family: 'Inter' } } }, x: { ticks: { font: { family: 'Inter' } } } } } }); } /** * Handles the PDF download functionality. * Captures the results output and chart canvases to generate a PDF report. */ window.downloadPdf = async function() { if (typeof html2canvas === 'undefined' || typeof jspdf === 'undefined' || typeof jspdf.jsPDF === 'undefined') { displayMessage('PDF generation libraries not loaded. Please try again or refresh.'); return; } hideMessage(); const { jsPDF } = jspdf; const doc = new jsPDF('p', 'pt', 'a4'); // Temporarily show the charts tab to ensure they render on canvas before capturing const chartsTabWasHidden = chartsTab.classList.contains('hidden'); if (chartsTabWasHidden) { chartsTab.classList.remove('hidden'); } // Elements to capture for PDF: results table and then each chart canvas const elementsToCapture = [ document.getElementById('resultsOutput'), document.getElementById('allocationChart'), document.getElementById('metricsChart') ]; let yPos = 40; doc.setFontSize(22); doc.setTextColor(51, 51, 51); doc.text('Sustainable Portfolio Optimization Report', doc.internal.pageSize.getWidth() / 2, yPos, { align: 'center' }); yPos += 30; doc.setFontSize(12); doc.setTextColor(100, 100, 100); doc.text(`Generated on: ${new Date().toLocaleDateString()}`, doc.internal.pageSize.getWidth() / 2, yPos, { align: 'center' }); yPos += 40; for (const element of elementsToCapture) { try { // For chart canvases, ensure they are rendered. For other elements, ensure visibility if needed. let elementToRender = element; if (element.tagName.toLowerCase() === 'canvas') { if ((element.id === 'allocationChart' && allocationChartInstance === null) || (element.id === 'metricsChart' && metricsChartInstance === null)) { console.warn(`Chart ${element.id} not yet rendered for PDF capture. Skipping.`); continue; } } else { // For non-canvas elements, ensure they are visible for html2canvas const wasHidden = element.classList.contains('hidden'); if (wasHidden) { element.classList.remove('hidden'); } // Capture the element as a canvas elementToRender = await html2canvas(element, { scale: 2, useCORS: true, backgroundColor: '#ffffff' }); if (wasHidden) { element.classList.add('hidden'); // Restore hidden state } } const imgData = elementToRender.toDataURL('image/png'); const imgWidth = 550; // Desired width for image in PDF const imgHeight = (elementToRender.height * imgWidth) / elementToRender.width; if (yPos + imgHeight > doc.internal.pageSize.getHeight() - 40) { doc.addPage(); yPos = 40; } doc.addImage(imgData, 'PNG', (doc.internal.pageSize.getWidth() - imgWidth) / 2, yPos, imgWidth, imgHeight); yPos += imgHeight + 30; } catch (error) { console.error('Error capturing element for PDF:', error); displayMessage('Failed to generate part of the PDF. Please ensure all data is loaded and visible before downloading.'); } } // Restore charts tab hidden state if it was temporarily shown if (chartsTabWasHidden) { chartsTab.classList.add('hidden'); } doc.save('Sustainable_Portfolio_Optimizer_Report.pdf'); }; // Initial setup: Populate asset selection and show the input tab when the DOM is ready populateAssetSelection(); showTab('input'); });