diff --git a/src/app/components/FireCalculatorForm.tsx b/src/app/components/FireCalculatorForm.tsx
index bbb54eb..dd42900 100644
--- a/src/app/components/FireCalculatorForm.tsx
+++ b/src/app/components/FireCalculatorForm.tsx
@@ -11,7 +11,12 @@ import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from '@/components/ui/form';
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
-import { ChartContainer, ChartTooltip, ChartTooltipContent } from '@/components/ui/chart';
+import {
+ ChartContainer,
+ ChartTooltip,
+ ChartTooltipContent,
+ type ChartConfig,
+} from '@/components/ui/chart';
import {
Area,
AreaChart,
@@ -24,7 +29,7 @@ import {
} from 'recharts';
import { Slider } from '@/components/ui/slider';
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
-import type { NameType, ValueType } from 'recharts/types/component/DefaultTooltipContent';
+import type { NameType, Payload, ValueType } from 'recharts/types/component/DefaultTooltipContent';
import { Calculator, Info, Share2, Check } from 'lucide-react';
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip';
import BlurThing from './blur-thing';
@@ -87,20 +92,57 @@ const formatNumber = (value: number | null) => {
}).format(value);
};
-// Helper function to render tooltip for chart
-const tooltipRenderer = ({ active, payload }: TooltipProps) => {
- if (active && payload?.[0]?.payload) {
- const data = payload[0].payload as YearlyData;
- return (
-
-
{`Year: ${data.year.toString()} (Age: ${data.age.toString()})`}
-
{`Median Balance: ${formatNumber(data.balanceP50 ?? data.balance)}`}
-
{`Monthly allowance: ${formatNumber(data.monthlyAllowance)}`}
-
{`Phase: ${data.phase === 'accumulation' ? 'Accumulation' : 'Retirement'}`}
-
- );
+const formatNumberShort = (value: number) => {
+ if (value >= 1000000) {
+ return `${(value / 1000000).toPrecision(3)}M`;
+ } else if (value >= 1000) {
+ return `${(value / 1000).toPrecision(3)}K`;
+ } else if (value <= -1000000) {
+ return `${(value / 1000000).toPrecision(3)}M`;
+ } else if (value <= -1000) {
+ return `${(value / 1000).toPrecision(3)}K`;
}
- return null;
+ return value.toString();
+};
+
+// Chart tooltip with the same styling as ChartTooltipContent, but with our custom label info
+const tooltipRenderer = ({ active, payload, label }: TooltipProps) => {
+ const allowedKeys = new Set(['balance', 'monthlyAllowance']);
+ const filteredPayload: Payload[] = (payload ?? [])
+ .filter(
+ (item): item is Payload =>
+ typeof item.dataKey === 'string' && allowedKeys.has(item.dataKey),
+ )
+ .map((item) => ({
+ ...item,
+ value: formatNumberShort(item.value as number),
+ }));
+ const safeLabel = typeof label === 'string' || typeof label === 'number' ? label : undefined;
+
+ return (
+ []) => {
+ const point = items.length > 0 ? (items[0]?.payload as YearlyData | undefined) : undefined;
+ if (!point) {
+ return null;
+ }
+
+ const phaseLabel = point.phase === 'retirement' ? 'Retirement phase' : 'Accumulation phase';
+
+ return (
+
+ {`Year ${String(point.year)} (Age ${String(point.age)})`}
+ {phaseLabel}
+
+ );
+ }}
+ />
+ );
};
export default function FireCalculatorForm({
@@ -402,6 +444,28 @@ export default function FireCalculatorForm({
mcRange: (row.balanceP90 ?? 0) - (row.balanceP10 ?? 0),
})) ?? [];
+ const projectionChartConfig: ChartConfig = {
+ year: {
+ label: 'Year',
+ },
+ balance: {
+ label: 'Balance',
+ color: 'var(--color-orange-500)',
+ },
+ balanceP10: {
+ label: 'P10 balance',
+ color: 'var(--color-orange-500)',
+ },
+ balanceP90: {
+ label: 'P90 balance',
+ color: 'var(--color-orange-500)',
+ },
+ monthlyAllowance: {
+ label: 'Monthly allowance',
+ color: 'var(--color-secondary)',
+ },
+ };
+
return (
<>
@@ -857,7 +921,7 @@ export default function FireCalculatorForm({
Shaded band shows 40th-60th percentile outcomes across 2000 simulations.
)}
-
+
{
- if (value >= 1000000) {
- return `${(value / 1000000).toPrecision(3)}M`;
- } else if (value >= 1000) {
- return `${(value / 1000).toPrecision(3)}K`;
- } else if (value <= -1000000) {
- return `${(value / 1000000).toPrecision(3)}M`;
- } else if (value <= -1000) {
- return `${(value / 1000).toPrecision(3)}K`;
- }
- return value.toString();
- }}
+ tickFormatter={formatNumberShort}
width={30}
stroke="var(--color-orange-500)"
tick={{}}
@@ -892,37 +945,19 @@ export default function FireCalculatorForm({
{
- if (value >= 1000000) {
- return `${(value / 1000000).toPrecision(3)}M`;
- } else if (value >= 1000) {
- return `${(value / 1000).toPrecision(3)}K`;
- }
- return value.toString();
- }}
+ tickFormatter={formatNumberShort}
width={30}
- stroke="var(--color-red-600)"
- />
- {
- return value;
- }}
- labelKey="year"
- indicator="line"
- />
- }
+ stroke="var(--color-primary)"
/>
+
-
+
-
-
+
+