Smart Beta Investing Performance Analyzer
Analysis Parameters
Please fill in all required fields and ensure dates are valid.
Performance Results
Run the analysis in the Input tab to see results.
Cumulative Performance Chart
Run the analysis in the Input tab to generate charts.
Note: Performance data is simulated for demonstration purposes. In a real-world application, this would fetch live financial data.
`; resultsOutput.innerHTML = tableHtml; } /** * Renders the cumulative performance chart. * @param {Object} analysisResults - Results from analyzePerformance. */ function renderChart(analysisResults) { if (!analysisResults.chartData || Object.keys(analysisResults.chartData).length === 0) { chartPlaceholder.classList.remove('hidden'); if (performanceChartInstance) { performanceChartInstance.destroy(); performanceChartInstance = null; } return; } chartPlaceholder.classList.add('hidden'); // Hide placeholder if chart can be rendered const ctx = document.getElementById('performanceChart').getContext('2d'); // Destroy existing chart instance if it exists to prevent overlap if (performanceChartInstance) { performanceChartInstance.destroy(); } const datasets = []; const colors = [ '#3B82F6', '#EF4444', '#10B981', '#F59E0B', '#6366F1', '#EC4899', '#A855F7', '#06B6D4' ]; let colorIndex = 0; Object.keys(analysisResults.chartData).forEach(seriesName => { const data = analysisResults.chartData[seriesName]; if (data && data.length > 0) { datasets.push({ label: seriesName, data: data, borderColor: colors[colorIndex % colors.length], backgroundColor: colors[colorIndex % colors.length] + '33', // 20% opacity borderWidth: 2, tension: 0.3, // Smooth curve pointRadius: 0, // Hide points fill: false, // Don't fill area under curve }); colorIndex++; } }); performanceChartInstance = new Chart(ctx, { type: 'line', // Line chart for cumulative performance data: { labels: analysisResults.chartDates, datasets: datasets }, options: { responsive: true, maintainAspectRatio: false, plugins: { legend: { display: true, position: 'top', labels: { font: { size: 14, family: 'Inter', }, color: '#333' } }, title: { display: true, text: `Cumulative Performance (Starting with $100)`, font: { size: 18, family: 'Inter', weight: 'bold' }, color: '#1F2937' }, tooltip: { mode: 'index', intersect: false, callbacks: { label: function(context) { let label = context.dataset.label || ''; if (label) { label += ': '; } if (context.parsed.y !== null) { label += `$${parseFloat(context.parsed.y).toFixed(2)}`; } return label; }, title: function(context) { return context[0].label; // Date } } } }, scales: { y: { beginAtZero: false, title: { display: true, text: 'Cumulative Value ($)', font: { size: 14, family: 'Inter' }, color: '#333' }, ticks: { callback: function(value) { return '$' + value; }, font: { family: 'Inter' } } }, x: { title: { display: true, text: 'Date', font: { size: 14, family: 'Inter' }, color: '#333' }, ticks: { font: { family: 'Inter' } } } } } }); } /** * Handles the PDF download functionality. * Captures the results output and chart canvas to generate a PDF report. */ window.downloadPdf = async function() { // Ensure jsPDF and html2canvas libraries are loaded if (typeof html2canvas === 'undefined' || typeof jspdf === 'undefined' || typeof jspdf.jsPDF === 'undefined') { displayMessage('PDF generation libraries not loaded. Please try again or refresh.'); return; } hideMessage(); // Clear any existing messages const { jsPDF } = jspdf; const doc = new jsPDF('p', 'pt', 'a4'); // 'p' for portrait, 'pt' for points, 'a4' for A4 size // Define the elements to be captured for the PDF report const elementsToCapture = [ document.getElementById('resultsOutput'), document.getElementById('performanceChart') ]; // Create promises for capturing each element as a canvas image const promises = elementsToCapture.map(element => { // Temporarily make the element visible if it's currently hidden const wasHidden = element.classList.contains('hidden'); if (wasHidden) { element.classList.remove('hidden'); } return html2canvas(element, { scale: 2, // Increase scale for better resolution in PDF useCORS: true, // Required for images/fonts loaded from other origins if any backgroundColor: '#ffffff' // Ensure white background for captured content }).then(canvas => { // Restore hidden state if it was temporarily made visible if (wasHidden) { element.classList.add('hidden'); } return canvas; }); }); let yPos = 40; // Initial Y position for content in the PDF // Add a main title to the PDF report doc.setFontSize(22); doc.setTextColor(51, 51, 51); // Dark gray doc.text('Smart Beta Investing Performance Analysis Report', doc.internal.pageSize.getWidth() / 2, yPos, { align: 'center' }); yPos += 30; // Move down for next element // Add generation date doc.setFontSize(12); doc.setTextColor(100, 100, 100); // Medium gray doc.text(`Generated on: ${new Date().toLocaleDateString()}`, doc.internal.pageSize.getWidth() / 2, yPos, { align: 'center' }); yPos += 40; // Move down for next element // Iterate through the captured canvases and add them to the PDF for (const promise of promises) { try { const canvas = await promise; const imgData = canvas.toDataURL('image/png'); // Convert canvas to PNG data URL const imgWidth = 550; // Desired width for the image in the PDF (A4 is approx 595pt wide) const imgHeight = (canvas.height * imgWidth) / canvas.width; // Maintain aspect ratio // Check if the image fits on the current page. If not, add a new page. if (yPos + imgHeight > doc.internal.pageSize.getHeight() - 40) { // Check against a 40pt bottom margin doc.addPage(); // Add a new page yPos = 40; // Reset Y position for the new page } // Add the image to the PDF, centered horizontally doc.addImage(imgData, 'PNG', (doc.internal.pageSize.getWidth() - imgWidth) / 2, yPos, imgWidth, imgHeight); yPos += imgHeight + 30; // Move Y position down, adding some padding } catch (error) { console.error('Error capturing element for PDF:', error); displayMessage('Failed to generate part of the PDF. Please ensure all data is loaded.'); } } // Save the generated PDF file doc.save('Smart_Beta_Performance_Analysis.pdf'); }; // Initial setup: Show the input tab and update navigation buttons when the DOM is ready showTab('input'); });