← back to Stayclaim
src/components/CurveSparkline.tsx
88 lines
/**
* CurveSparkline — server-rendered SVG of the LA 1978-1984 cycle.
* Reads the same data file the projection uses so the anchor and the
* picture cannot drift apart.
*/
import fs from 'node:fs';
import path from 'node:path';
type Point = { year: number; value: number; yoy_pct: number | null };
let cached: Point[] | null = null;
function loadPriceYoy(): Point[] | null {
if (cached) return cached;
const p = path.resolve(process.cwd(), 'data/multiplier_1978_1984.json');
if (!fs.existsSync(p)) return null;
const j = JSON.parse(fs.readFileSync(p, 'utf8'));
cached = j.price_yoy as Point[];
return cached;
}
export function CurveSparkline({ height = 64, className = '' }: { height?: number; className?: string }) {
const series = loadPriceYoy();
if (!series || series.length < 2) return null;
const w = 320;
const h = height;
const pad = 6;
const xs = series.map(p => p.year);
const ys = series.map(p => p.value);
const minX = Math.min(...xs);
const maxX = Math.max(...xs);
const minY = Math.min(...ys);
const maxY = Math.max(...ys);
const range = Math.max(1, maxY - minY);
const x = (year: number) => pad + ((year - minX) / (maxX - minX)) * (w - 2 * pad);
const y = (val: number) => h - pad - ((val - minY) / range) * (h - 2 * pad);
const path = series.map((p, i) => `${i === 0 ? 'M' : 'L'} ${x(p.year).toFixed(1)} ${y(p.value).toFixed(1)}`).join(' ');
return (
<figure className={`mt-3 ${className}`}>
<svg
viewBox={`0 0 ${w} ${h}`}
width="100%"
height={h}
role="img"
aria-label="LA County home-price index, 1978–1984"
className="overflow-visible"
>
{/* Y baseline */}
<line
x1={pad}
x2={w - pad}
y1={h - pad}
y2={h - pad}
stroke="#1a141015"
strokeWidth={1}
/>
{/* curve */}
<path d={path} fill="none" stroke="#4a5d45" strokeWidth={1.5} />
{/* points */}
{series.map(p => (
<circle key={p.year} cx={x(p.year)} cy={y(p.value)} r={2.2} fill="#4a5d45" />
))}
{/* year labels */}
{series.map(p => (
<text
key={`l-${p.year}`}
x={x(p.year)}
y={h - 2}
fontSize={9}
textAnchor="middle"
fill="#1a141060"
fontFamily="IBM Plex Mono, monospace"
style={{ letterSpacing: 0.5 }}
>
{String(p.year).slice(2)}
</text>
))}
</svg>
<figcaption className="font-mono text-[10px] text-ink/50 mt-1">
LA County HPI · {minX}–{maxX} · FRED ATNHPIUS06037A
</figcaption>
</figure>
);
}