mirror of
https://github.com/wahyd4/links.git
synced 2026-08-09 05:06:16 +10:00
Remove one chart
This commit is contained in:
@@ -322,6 +322,11 @@
|
||||
<label class="input-label" for="inflationRate">Inflation Rate (%)</label>
|
||||
<input type="number" id="inflationRate" class="input-field" value="2.5" min="0" max="20" step="0.1">
|
||||
</div>
|
||||
|
||||
<div class="input-group">
|
||||
<label class="input-label" for="incomeGrowthRate">Income Growth (%)</label>
|
||||
<input type="number" id="incomeGrowthRate" class="input-field" value="0" min="0" max="20" step="0.1">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex gap-2 mt-3 items-center">
|
||||
@@ -408,7 +413,7 @@
|
||||
Quick Adjustments - See Impact Instantly
|
||||
</h3>
|
||||
|
||||
<div class="grid grid-cols-2 md:grid-cols-4 gap-2">
|
||||
<div class="grid grid-cols-2 md:grid-cols-5 gap-2">
|
||||
<!-- Annual Expenses Adjustment -->
|
||||
<div class="bg-gradient-to-br from-blue-50 to-blue-100 p-2 rounded border border-blue-200">
|
||||
<div class="flex items-center justify-between mb-1">
|
||||
@@ -468,6 +473,21 @@
|
||||
<button onclick="adjustValue('targetAge', 1)" class="px-2 py-0.5 bg-white text-gray-700 rounded text-xs hover:bg-orange-200 transition border border-orange-300">+1y</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Income Growth Rate Adjustment -->
|
||||
<div class="bg-gradient-to-br from-teal-50 to-teal-100 p-2 rounded border border-teal-200">
|
||||
<div class="flex items-center justify-between mb-1">
|
||||
<label class="text-xs font-semibold text-gray-700">
|
||||
<i class="fas fa-arrow-trend-up mr-1 text-teal-600"></i>
|
||||
Income Growth
|
||||
</label>
|
||||
<span id="quickIncomeGrowthValue" class="text-xs font-mono bg-white px-1 py-0.5 rounded">0.0%</span>
|
||||
</div>
|
||||
<div class="flex gap-1 flex-wrap">
|
||||
<button onclick="adjustValue('incomeGrowthRate', -1)" class="px-2 py-0.5 bg-white text-gray-700 rounded text-xs hover:bg-teal-200 transition border border-teal-300">-1%</button>
|
||||
<button onclick="adjustValue('incomeGrowthRate', 1)" class="px-2 py-0.5 bg-white text-gray-700 rounded text-xs hover:bg-teal-200 transition border border-teal-300">+1%</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -486,21 +506,6 @@
|
||||
<canvas id="netWorthChart" style="max-height: 400px;"></canvas>
|
||||
</div>
|
||||
|
||||
<!-- Progress Chart -->
|
||||
<div class="chart-container">
|
||||
<h3 class="chart-title">
|
||||
<i class="fas fa-percentage mr-2"></i>
|
||||
FIRE Progress Over Time
|
||||
</h3>
|
||||
<p class="text-sm text-gray-600 mb-3">
|
||||
<i class="fas fa-info-circle mr-1"></i>
|
||||
This shows your progress toward FIRE as a percentage. The calculation is:
|
||||
<strong>(Your Net Worth ÷ Inflation-Adjusted FIRE Target) × 100%</strong>.
|
||||
When you hit 100%, you've reached FIRE!
|
||||
</p>
|
||||
<canvas id="progressChart" style="max-height: 300px;"></canvas>
|
||||
</div>
|
||||
|
||||
<!-- Year-by-Year Data Table -->
|
||||
<div class="chart-container">
|
||||
<h3 class="chart-title">
|
||||
@@ -573,7 +578,6 @@
|
||||
|
||||
let netWorthChart = null;
|
||||
let monteCarloChart = null;
|
||||
let progressChart = null;
|
||||
|
||||
// Custom plugin to draw vertical line at retirement age
|
||||
const verticalLinePlugin = {
|
||||
@@ -649,13 +653,14 @@
|
||||
function generateProjections(inputs) {
|
||||
const projections = [];
|
||||
const yearsToProject = inputs.targetAge - inputs.currentAge;
|
||||
const annualSavings = inputs.annualIncome * inputs.savingsRate;
|
||||
let currentIncome = inputs.annualIncome;
|
||||
let netWorth = inputs.currentNetWorth;
|
||||
|
||||
for (let year = 0; year <= yearsToProject; year++) {
|
||||
const age = inputs.currentAge + year;
|
||||
const currentYear = new Date().getFullYear() + year;
|
||||
const fireTarget = inputs.fireTarget * Math.pow(1 + inputs.inflationRate, year);
|
||||
const annualSavings = currentIncome * inputs.savingsRate;
|
||||
const progress = (netWorth / fireTarget) * 100;
|
||||
|
||||
projections.push({
|
||||
@@ -667,8 +672,9 @@
|
||||
annualSavings: annualSavings
|
||||
});
|
||||
|
||||
// Project next year's net worth
|
||||
// Project next year's net worth and income
|
||||
netWorth = (netWorth + annualSavings) * (1 + inputs.returnRate);
|
||||
currentIncome = currentIncome * (1 + inputs.incomeGrowthRate);
|
||||
}
|
||||
|
||||
return projections;
|
||||
@@ -772,7 +778,7 @@
|
||||
newValue = Math.max(min, Math.min(max, newValue));
|
||||
|
||||
// Round to appropriate precision
|
||||
if (fieldId === 'returnRate' || fieldId === 'inflationRate') {
|
||||
if (fieldId === 'returnRate' || fieldId === 'inflationRate' || fieldId === 'incomeGrowthRate') {
|
||||
newValue = Math.round(newValue * 10) / 10; // 1 decimal place
|
||||
} else {
|
||||
newValue = Math.round(newValue);
|
||||
@@ -793,11 +799,13 @@
|
||||
const returnRate = parseFloat(document.getElementById('returnRate').value) || 0;
|
||||
const annualIncome = parseFloat(document.getElementById('annualIncome').value) || 0;
|
||||
const targetAge = parseInt(document.getElementById('targetAge').value) || 0;
|
||||
const incomeGrowthRate = parseFloat(document.getElementById('incomeGrowthRate').value) || 0;
|
||||
|
||||
document.getElementById('quickExpensesValue').textContent = formatCurrency(annualExpenses);
|
||||
document.getElementById('quickReturnValue').textContent = returnRate.toFixed(1) + '%';
|
||||
document.getElementById('quickIncomeValue').textContent = formatCurrency(annualIncome);
|
||||
document.getElementById('quickTargetAgeValue').textContent = targetAge;
|
||||
document.getElementById('quickIncomeGrowthValue').textContent = incomeGrowthRate.toFixed(1) + '%';
|
||||
}
|
||||
|
||||
// Get input values
|
||||
@@ -816,6 +824,7 @@
|
||||
savingsRate: savingsRate,
|
||||
returnRate: parseFloat(document.getElementById('returnRate').value) / 100 || 0.08,
|
||||
inflationRate: parseFloat(document.getElementById('inflationRate').value) / 100 || 0.025,
|
||||
incomeGrowthRate: parseFloat(document.getElementById('incomeGrowthRate').value) / 100 || 0,
|
||||
// Calculate FIRE target using 4% rule
|
||||
fireTarget: expenses * 25
|
||||
};
|
||||
@@ -828,17 +837,19 @@
|
||||
|
||||
for (let sim = 0; sim < numSimulations; sim++) {
|
||||
let netWorth = inputs.currentNetWorth;
|
||||
let currentIncome = inputs.annualIncome;
|
||||
let yearToFire = null;
|
||||
|
||||
for (let year = 0; year < maxYears; year++) {
|
||||
const age = inputs.currentAge + year;
|
||||
const annualSavings = inputs.annualIncome * inputs.savingsRate;
|
||||
const annualSavings = currentIncome * inputs.savingsRate;
|
||||
|
||||
// Add randomness to returns (normal distribution)
|
||||
const volatility = 0.15; // 15% standard deviation
|
||||
const randomReturn = inputs.returnRate + (Math.random() - 0.5) * 2 * volatility;
|
||||
|
||||
netWorth = (netWorth + annualSavings) * (1 + randomReturn);
|
||||
currentIncome = currentIncome * (1 + inputs.incomeGrowthRate);
|
||||
|
||||
// Adjust FIRE target for inflation
|
||||
const inflationAdjustedTarget = inputs.fireTarget * Math.pow(1 + inputs.inflationRate, year);
|
||||
@@ -898,7 +909,6 @@
|
||||
// Update all charts
|
||||
updateNetWorthChart(projections, inputs);
|
||||
updateMonteCarloChart(inputs, p10, p25, p50, p75, p90);
|
||||
updateProgressChart(projections);
|
||||
}
|
||||
|
||||
// Show results
|
||||
@@ -1122,82 +1132,6 @@
|
||||
});
|
||||
}
|
||||
|
||||
// Update progress chart
|
||||
function updateProgressChart(projections) {
|
||||
if (progressChart) {
|
||||
progressChart.destroy();
|
||||
}
|
||||
|
||||
const ages = projections.map(p => p.age);
|
||||
const progress = projections.map(p => Math.min(p.progress, 150)); // Cap at 150% for chart
|
||||
|
||||
const ctx = document.getElementById('progressChart').getContext('2d');
|
||||
progressChart = new Chart(ctx, {
|
||||
type: 'line',
|
||||
data: {
|
||||
labels: ages,
|
||||
datasets: [{
|
||||
label: 'Progress to FIRE',
|
||||
data: progress,
|
||||
borderColor: '#3498db',
|
||||
backgroundColor: 'rgba(52, 152, 219, 0.2)',
|
||||
borderWidth: 3,
|
||||
fill: true,
|
||||
tension: 0.4,
|
||||
pointRadius: 0
|
||||
}, {
|
||||
label: '100% (FIRE Achieved)',
|
||||
data: Array(ages.length).fill(100),
|
||||
borderColor: '#27ae60',
|
||||
backgroundColor: 'transparent',
|
||||
borderWidth: 2,
|
||||
borderDash: [5, 5],
|
||||
fill: false,
|
||||
pointRadius: 0
|
||||
}]
|
||||
},
|
||||
options: {
|
||||
responsive: true,
|
||||
maintainAspectRatio: true,
|
||||
plugins: {
|
||||
legend: {
|
||||
display: true,
|
||||
position: 'top'
|
||||
},
|
||||
tooltip: {
|
||||
mode: 'index',
|
||||
intersect: false,
|
||||
callbacks: {
|
||||
label: function(context) {
|
||||
if (context.datasetIndex === 0) {
|
||||
return 'Progress: ' + context.parsed.y.toFixed(1) + '%';
|
||||
}
|
||||
return context.dataset.label;
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
scales: {
|
||||
y: {
|
||||
beginAtZero: true,
|
||||
max: 150,
|
||||
ticks: {
|
||||
callback: function(value) {
|
||||
return value + '%';
|
||||
}
|
||||
}
|
||||
},
|
||||
x: {
|
||||
title: {
|
||||
display: true,
|
||||
text: 'Age'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Reset form
|
||||
function resetForm() {
|
||||
document.getElementById('currentAge').value = 35;
|
||||
@@ -1209,7 +1143,9 @@
|
||||
document.getElementById('savingsRate').value = 40;
|
||||
document.getElementById('returnRate').value = 8;
|
||||
document.getElementById('inflationRate').value = 2.5;
|
||||
document.getElementById('incomeGrowthRate').value = 0;
|
||||
|
||||
updateQuickAdjustmentDisplays();
|
||||
document.getElementById('resultsSection').classList.remove('show');
|
||||
}
|
||||
|
||||
@@ -1250,7 +1186,8 @@
|
||||
'annualIncome',
|
||||
'savingsRate',
|
||||
'returnRate',
|
||||
'inflationRate'
|
||||
'inflationRate',
|
||||
'incomeGrowthRate'
|
||||
];
|
||||
|
||||
// Add input event listeners for auto-calculation
|
||||
|
||||
Reference in New Issue
Block a user