86 lines
2.7 KiB
TypeScript
86 lines
2.7 KiB
TypeScript
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>
|
||
);
|
||
} |