website/hub/services/static/js/roi-calculator/chart-manager.js

216 lines
No EOL
9.3 KiB
JavaScript

/**
* 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 = `<div class="text-muted text-center"><i class="bi bi-exclamation-triangle"></i><br>${message}</div>`;
}
});
}
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);
}
}
}