Money forecast: calm optimistic start, rising plateau, thin solid projected lines
All checks were successful
Deploy production / deploy (push) Successful in 1m13s
All checks were successful
Deploy production / deploy (push) Successful in 1m13s
- Optimistic: no crash or correction through 2031 (5 years from today), earning the 9.5% average; cycle resumes after (correction 2035, crash 2040). Salary plateau grows 2%/yr real so pay never stalls. - Realistic keeps its single -30% crash in 2029; pessimistic unchanged. - Projected lines are now thin, slightly lighter solid lines in every view; legend updated. - Explanation text lists the crash/correction years and recovery time.
This commit is contained in:
@@ -15,8 +15,8 @@ export type ChartSeries = {
|
||||
color: string;
|
||||
points: ChartPoint[];
|
||||
emphasis?: boolean;
|
||||
/** Points after this date are drawn dashed (projected). */
|
||||
dashedFrom?: string;
|
||||
/** Points after this date are projected: drawn as a thinner, lighter solid line. */
|
||||
projectedFrom?: string;
|
||||
};
|
||||
|
||||
const palette = ['#0070f3', '#e5484d', '#46a758', '#ad7f00', '#8e4ec6', '#0091ad', '#e56910'];
|
||||
@@ -283,7 +283,7 @@ export function wealthProjection(
|
||||
/**
|
||||
* Thins long series for drawing: per time bucket only the lowest and highest point stay (in
|
||||
* date order), so a daily projection over decades keeps its spikes without plotting every day.
|
||||
* The first and last points and the solid/dashed join are always kept.
|
||||
* The first and last points and the actual/projected join are always kept.
|
||||
*/
|
||||
export function decimateSeries(series: ChartSeries[], buckets: number): ChartSeries[] {
|
||||
const limit = Math.max(2, Math.floor(buckets)) * 2;
|
||||
@@ -301,8 +301,8 @@ export function decimateSeries(series: ChartSeries[], buckets: number): ChartSer
|
||||
const keep = new Uint8Array(line.points.length);
|
||||
keep[0] = 1;
|
||||
keep[line.points.length - 1] = 1;
|
||||
if (line.dashedFrom) {
|
||||
const join = line.points.findLastIndex((point) => point.date <= line.dashedFrom!);
|
||||
if (line.projectedFrom) {
|
||||
const join = line.points.findLastIndex((point) => point.date <= line.projectedFrom!);
|
||||
if (join >= 0) keep[join] = 1;
|
||||
}
|
||||
let bucket = -1;
|
||||
@@ -334,7 +334,7 @@ export function decimateSeries(series: ChartSeries[], buckets: number): ChartSer
|
||||
export type TimelineView = 'total' | 'split' | 'accounts';
|
||||
|
||||
/**
|
||||
* Joins actual history (solid, through today) with the wealth projection (dashed, after
|
||||
* Joins actual history (solid, through today) with the wealth projection (thin solid line, after
|
||||
* today) so both read as one continuous line per measure.
|
||||
*/
|
||||
export function wealthTimeline(
|
||||
@@ -379,7 +379,7 @@ export function wealthTimeline(
|
||||
label: 'Total',
|
||||
color: 'var(--site-text)',
|
||||
emphasis: true,
|
||||
dashedFrom: today,
|
||||
projectedFrom: today,
|
||||
points: join(history.at(-1)!.points, totalProjection?.points ?? [])
|
||||
};
|
||||
if (view === 'accounts')
|
||||
@@ -395,13 +395,13 @@ export function wealthTimeline(
|
||||
{
|
||||
label: 'Spendable',
|
||||
color: '#46a758',
|
||||
dashedFrom: today,
|
||||
projectedFrom: today,
|
||||
points: join(sumKind(false), spendableProjection?.points ?? [])
|
||||
},
|
||||
{
|
||||
label: 'Invested',
|
||||
color: '#8e4ec6',
|
||||
dashedFrom: today,
|
||||
projectedFrom: today,
|
||||
points: join(sumKind(true), investedProjection?.points ?? [])
|
||||
}
|
||||
];
|
||||
|
||||
Reference in New Issue
Block a user