finance-tools/src/components/YieldCurve.tsx

86 lines
2.7 KiB
TypeScript
Raw Normal View History

2026-06-21 20:28:06 +00:00
import { Tag } from "./ui";
import { AppIcon } from "./PixelIcons";
import type { MacroPayload } from "../lib/api";
/** 殖利率曲線小圖:留白充足、線條偏細,縮小時用 viewBox 等比縮放 */
export default function YieldCurve({ yc }: { yc?: MacroPayload["yieldCurve"] }) {
if (!yc?.yields?.length || !yc.maturities?.length)
return <p className="muted small"></p>;
const ys = yc.yields;
const labels = yc.maturities;
const min = Math.min(...ys, ...(yc.prevYields || []));
const max = Math.max(...ys, ...(yc.prevYields || []));
const span = max - min || 1;
const W = 360;
const H = 128;
const padL = 28;
const padR = 20;
const padT = 22;
const padB = 28;
const plotW = W - padL - padR;
const plotH = H - padT - padB;
const x = (i: number) => padL + (plotW * i) / Math.max(1, labels.length - 1);
const y = (v: number) => padT + plotH * (1 - (v - min) / span);
const path = (arr: number[]) => arr.map((v, i) => `${x(i)},${y(v)}`).join(" ");
const yTicks = 3;
const gridYs = Array.from({ length: yTicks + 1 }, (_, i) => padT + (plotH * i) / yTicks);
return (
<div className="chart-panel chart-panel--curve">
{yc.inverted && (
<div className="chart-panel-tag">
<Tag tone="down">
<span className="tag-icon-inline">
<AppIcon name="warning" size={14} framed={false} />
</span>
</Tag>
</div>
)}
<svg viewBox={`0 0 ${W} ${H}`} className="mini-chart-svg" role="img" aria-label="殖利率曲線">
{gridYs.map((gy, i) => (
<line
key={i}
x1={padL}
x2={W - padR}
y1={gy}
y2={gy}
stroke="rgba(231,198,107,.06)"
strokeWidth="1"
/>
))}
{yc.prevYields?.length === ys.length && (
<polyline
points={path(yc.prevYields)}
fill="none"
stroke="rgba(174,180,207,.35)"
strokeWidth="1.5"
strokeDasharray="4 4"
strokeLinecap="round"
/>
)}
<polyline
points={path(ys)}
fill="none"
stroke="#6fe0d0"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
/>
{ys.map((v, i) => (
<g key={i}>
<circle cx={x(i)} cy={y(v)} r="2.5" fill="#e7c66b" stroke="#0c1024" strokeWidth="1" />
<text x={x(i)} y={H - 8} fontSize="8" fill="#8b92b0" textAnchor="middle" fontFamily="var(--sans)">
{labels[i]}
</text>
</g>
))}
</svg>
<div className="chart-caption"></div>
</div>
);
}