/** * Chart Management Module * Handles Chart.js initialization and updates */ class ChartManager { constructor(calculator) { this.calculator = calculator; this.charts = {}; } initializeCharts() { // Check if Chart.js is available if (typeof Chart === 'undefined') { console.error('Chart.js library not loaded. Charts will not be available.'); this.showChartError('Chart.js library failed to load. Please refresh the page.'); return; } try { // ROI Progression Chart (replaces Instance Growth Chart) const roiCanvas = document.getElementById('instanceGrowthChart'); if (!roiCanvas) { console.error('ROI progression chart canvas not found'); return; } const roiCtx = roiCanvas.getContext('2d'); this.charts.roiProgression = new Chart(roiCtx, { type: 'line', data: { labels: [], datasets: [] }, options: { responsive: true, maintainAspectRatio: false, plugins: { legend: { position: 'top' }, title: { display: true, text: 'ROI Progression Over Time' } }, scales: { y: { title: { display: true, text: 'ROI (%)' }, grid: { color: function(context) { return context.tick.value === 0 ? 'rgba(0,0,0,0.5)' : 'rgba(0,0,0,0.1)'; } } }, x: { title: { display: true, text: 'Month' } } } } }); // Net Position Chart (replaces simple Revenue Chart) const netPositionCanvas = document.getElementById('revenueChart'); if (!netPositionCanvas) { console.error('Net position chart canvas not found'); return; } const netPositionCtx = netPositionCanvas.getContext('2d'); this.charts.netPosition = new Chart(netPositionCtx, { type: 'line', data: { labels: [], datasets: [] }, options: { responsive: true, maintainAspectRatio: false, plugins: { legend: { position: 'top' }, title: { display: true, text: 'Net Financial Position (Break-Even Analysis)' } }, scales: { y: { title: { display: true, text: 'Net Position (CHF)' }, grid: { color: function(context) { return context.tick.value === 0 ? 'rgba(0,0,0,0.8)' : 'rgba(0,0,0,0.1)'; } } }, x: { title: { display: true, text: 'Month' } } } } }); // Model Comparison Chart (replaces generic Cash Flow Chart) const modelComparisonCanvas = document.getElementById('cashFlowChart'); if (!modelComparisonCanvas) { console.error('Model comparison chart canvas not found'); return; } const modelComparisonCtx = modelComparisonCanvas.getContext('2d'); this.charts.modelComparison = new Chart(modelComparisonCtx, { type: 'bar', data: { labels: [], datasets: [] }, options: { responsive: true, maintainAspectRatio: false, plugins: { legend: { position: 'top' }, title: { display: true, text: 'Investment Model Performance Comparison' } }, scales: { y: { beginAtZero: true, title: { display: true, text: 'Total Return (CHF)' } }, x: { title: { display: true, text: 'Growth Scenario' } } } } }); } catch (error) { console.error('Error initializing charts:', error); this.showChartError('Failed to initialize charts. Please refresh the page.'); } } showChartError(message) { // Show error message in place of charts const chartContainers = ['instanceGrowthChart', 'revenueChart', 'cashFlowChart']; chartContainers.forEach(containerId => { const container = document.getElementById(containerId); if (container) { container.style.display = 'flex'; container.style.justifyContent = 'center'; container.style.alignItems = 'center'; container.style.minHeight = '300px'; container.style.backgroundColor = '#f8f9fa'; container.style.border = '1px solid #dee2e6'; container.style.borderRadius = '4px'; container.innerHTML = `

${message}
`; } }); } updateCharts() { try { const scenarios = Object.keys(this.calculator.results); if (scenarios.length === 0 || !this.charts.roiProgression) return; const colors = { conservative: '#28a745', moderate: '#ffc107', aggressive: '#dc3545' }; // Get month labels const maxMonths = Math.max(...scenarios.map(s => this.calculator.monthlyData[s].length)); const monthLabels = Array.from({ length: maxMonths }, (_, i) => `M${i + 1}`); // Update ROI Progression Chart this.charts.roiProgression.data.labels = monthLabels; this.charts.roiProgression.data.datasets = scenarios.map(scenario => ({ label: `${this.calculator.scenarios[scenario].name} (${this.calculator.results[scenario].investmentModel})`, data: this.calculator.monthlyData[scenario].map(d => d.roiPercent), borderColor: colors[scenario], backgroundColor: colors[scenario] + '20', tension: 0.4, pointBackgroundColor: this.calculator.monthlyData[scenario].map(d => d.roiPercent >= 0 ? colors[scenario] : '#dc3545' ) })); this.charts.roiProgression.update(); // Update Net Position Chart (Break-Even Analysis) this.charts.netPosition.data.labels = monthLabels; this.charts.netPosition.data.datasets = scenarios.map(scenario => ({ label: `${this.calculator.scenarios[scenario].name} Net Position`, data: this.calculator.monthlyData[scenario].map(d => d.netPosition), borderColor: colors[scenario], backgroundColor: colors[scenario] + '20', tension: 0.4, fill: { target: 'origin', above: colors[scenario] + '10', below: '#dc354510' } })); this.charts.netPosition.update(); // Update Model Comparison Chart const scenarioLabels = scenarios.map(s => this.calculator.scenarios[s].name); const currentInvestmentModel = Object.values(this.calculator.results)[0]?.investmentModel || 'direct'; // Show comparison with both models for the same scenarios this.charts.modelComparison.data.labels = scenarioLabels; this.charts.modelComparison.data.datasets = [{ label: `${currentInvestmentModel === 'loan' ? 'Loan Model' : 'Direct Investment'} - Final Return`, data: scenarios.map(scenario => this.calculator.results[scenario].cspRevenue), backgroundColor: scenarios.map(scenario => colors[scenario] + '80'), borderColor: scenarios.map(scenario => colors[scenario]), borderWidth: 2 }]; // Add performance metrics for direct investment if (currentInvestmentModel === 'direct') { this.charts.modelComparison.data.datasets.push({ label: 'Performance Bonus Impact', data: scenarios.map(scenario => this.calculator.results[scenario].avgPerformanceBonus * this.calculator.results[scenario].cspRevenue ), backgroundColor: '#17a2b8', borderColor: '#17a2b8', borderWidth: 2 }); } this.charts.modelComparison.update(); } catch (error) { console.error('Error updating charts:', error); } } }