182 lines
7.1 KiB
JavaScript
182 lines
7.1 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 {
|
||
|
|
// Instance Growth Chart
|
||
|
|
const instanceCanvas = document.getElementById('instanceGrowthChart');
|
||
|
|
if (!instanceCanvas) {
|
||
|
|
console.error('Instance growth chart canvas not found');
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
const instanceCtx = instanceCanvas.getContext('2d');
|
||
|
|
this.charts.instanceGrowth = new Chart(instanceCtx, {
|
||
|
|
type: 'line',
|
||
|
|
data: { labels: [], datasets: [] },
|
||
|
|
options: {
|
||
|
|
responsive: true,
|
||
|
|
maintainAspectRatio: false,
|
||
|
|
plugins: {
|
||
|
|
legend: { position: 'top' }
|
||
|
|
},
|
||
|
|
scales: {
|
||
|
|
y: {
|
||
|
|
beginAtZero: true,
|
||
|
|
title: { display: true, text: 'Total Instances' }
|
||
|
|
},
|
||
|
|
x: {
|
||
|
|
title: { display: true, text: 'Month' }
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
});
|
||
|
|
|
||
|
|
// Revenue Chart
|
||
|
|
const revenueCanvas = document.getElementById('revenueChart');
|
||
|
|
if (!revenueCanvas) {
|
||
|
|
console.error('Revenue chart canvas not found');
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
const revenueCtx = revenueCanvas.getContext('2d');
|
||
|
|
this.charts.revenue = new Chart(revenueCtx, {
|
||
|
|
type: 'line',
|
||
|
|
data: { labels: [], datasets: [] },
|
||
|
|
options: {
|
||
|
|
responsive: true,
|
||
|
|
maintainAspectRatio: false,
|
||
|
|
plugins: {
|
||
|
|
legend: { position: 'top' }
|
||
|
|
},
|
||
|
|
scales: {
|
||
|
|
y: {
|
||
|
|
beginAtZero: true,
|
||
|
|
title: { display: true, text: 'Cumulative Revenue (CHF)' }
|
||
|
|
},
|
||
|
|
x: {
|
||
|
|
title: { display: true, text: 'Month' }
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
});
|
||
|
|
|
||
|
|
// Cash Flow Chart
|
||
|
|
const cashFlowCanvas = document.getElementById('cashFlowChart');
|
||
|
|
if (!cashFlowCanvas) {
|
||
|
|
console.error('Cash flow chart canvas not found');
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
const cashFlowCtx = cashFlowCanvas.getContext('2d');
|
||
|
|
this.charts.cashFlow = new Chart(cashFlowCtx, {
|
||
|
|
type: 'bar',
|
||
|
|
data: { labels: [], datasets: [] },
|
||
|
|
options: {
|
||
|
|
responsive: true,
|
||
|
|
maintainAspectRatio: false,
|
||
|
|
plugins: {
|
||
|
|
legend: { position: 'top' }
|
||
|
|
},
|
||
|
|
scales: {
|
||
|
|
y: {
|
||
|
|
title: { display: true, text: 'Monthly Cash Flow (CHF)' }
|
||
|
|
},
|
||
|
|
x: {
|
||
|
|
title: { display: true, text: 'Month' }
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
});
|
||
|
|
} 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.instanceGrowth) 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 Instance Growth Chart
|
||
|
|
this.charts.instanceGrowth.data.labels = monthLabels;
|
||
|
|
this.charts.instanceGrowth.data.datasets = scenarios.map(scenario => ({
|
||
|
|
label: this.calculator.scenarios[scenario].name,
|
||
|
|
data: this.calculator.monthlyData[scenario].map(d => d.totalInstances),
|
||
|
|
borderColor: colors[scenario],
|
||
|
|
backgroundColor: colors[scenario] + '20',
|
||
|
|
tension: 0.4
|
||
|
|
}));
|
||
|
|
this.charts.instanceGrowth.update();
|
||
|
|
|
||
|
|
// Update Revenue Chart
|
||
|
|
this.charts.revenue.data.labels = monthLabels;
|
||
|
|
this.charts.revenue.data.datasets = scenarios.map(scenario => ({
|
||
|
|
label: this.calculator.scenarios[scenario].name + ' (CSP)',
|
||
|
|
data: this.calculator.monthlyData[scenario].map(d => d.cumulativeCSPRevenue),
|
||
|
|
borderColor: colors[scenario],
|
||
|
|
backgroundColor: colors[scenario] + '20',
|
||
|
|
tension: 0.4
|
||
|
|
}));
|
||
|
|
this.charts.revenue.update();
|
||
|
|
|
||
|
|
// Update Cash Flow Chart (show average across scenarios)
|
||
|
|
const avgCashFlow = monthLabels.map((_, monthIndex) => {
|
||
|
|
const monthData = scenarios.map(scenario =>
|
||
|
|
this.calculator.monthlyData[scenario][monthIndex]?.cspRevenue || 0
|
||
|
|
);
|
||
|
|
return monthData.reduce((sum, val) => sum + val, 0) / monthData.length;
|
||
|
|
});
|
||
|
|
|
||
|
|
this.charts.cashFlow.data.labels = monthLabels;
|
||
|
|
this.charts.cashFlow.data.datasets = [{
|
||
|
|
label: 'Average Monthly CSP Revenue',
|
||
|
|
data: avgCashFlow,
|
||
|
|
backgroundColor: '#007bff'
|
||
|
|
}];
|
||
|
|
this.charts.cashFlow.update();
|
||
|
|
} catch (error) {
|
||
|
|
console.error('Error updating charts:', error);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|