75 lines
2.2 KiB
TypeScript
75 lines
2.2 KiB
TypeScript
import type { EvolutionPoint } from '../lib/eras';
|
|
|
|
/** 时速演进曲线(SVG):横轴年代、纵轴最高时速,体现"科学感"主线。*/
|
|
export function EvolutionCurve({ points }: { points: EvolutionPoint[] }) {
|
|
if (points.length < 2) return null;
|
|
const W = 720;
|
|
const H = 180;
|
|
const padX = 36;
|
|
const padY = 24;
|
|
const decades = points.map((p) => p.decade);
|
|
const speeds = points.map((p) => p.maxSpeed);
|
|
const minD = Math.min(...decades);
|
|
const maxD = Math.max(...decades);
|
|
const maxS = Math.max(...speeds);
|
|
const spanD = Math.max(1, maxD - minD);
|
|
|
|
const x = (d: number) => padX + ((d - minD) / spanD) * (W - padX * 2);
|
|
const y = (s: number) => H - padY - (s / maxS) * (H - padY * 2);
|
|
|
|
const line = points.map((p) => `${x(p.decade)},${y(p.maxSpeed)}`).join(' ');
|
|
const area = `${padX},${H - padY} ${line} ${W - padX},${H - padY}`;
|
|
|
|
return (
|
|
<svg
|
|
className="evo"
|
|
viewBox={`0 0 ${W} ${H}`}
|
|
role="img"
|
|
aria-label="中国机车最高时速演进曲线"
|
|
data-testid="evolution-curve"
|
|
>
|
|
<defs>
|
|
<linearGradient id="evoFill" x1="0" y1="0" x2="0" y2="1">
|
|
<stop offset="0" stopColor="#4ea1ff" stopOpacity="0.35" />
|
|
<stop offset="1" stopColor="#4ea1ff" stopOpacity="0" />
|
|
</linearGradient>
|
|
</defs>
|
|
<polygon points={area} fill="url(#evoFill)" />
|
|
<polyline
|
|
points={line}
|
|
fill="none"
|
|
stroke="#4ea1ff"
|
|
strokeWidth="2.5"
|
|
strokeLinejoin="round"
|
|
/>
|
|
{points.map((p) => (
|
|
<g key={p.decade}>
|
|
<circle cx={x(p.decade)} cy={y(p.maxSpeed)} r="3.5" fill="#9ed0ff" />
|
|
{(p.decade === minD ||
|
|
p.decade === maxD ||
|
|
p.maxSpeed === maxS) && (
|
|
<text
|
|
x={x(p.decade)}
|
|
y={y(p.maxSpeed) - 10}
|
|
fontSize="11"
|
|
fill="#cdd5e0"
|
|
textAnchor="middle"
|
|
>
|
|
{p.maxSpeed}km/h
|
|
</text>
|
|
)}
|
|
<text
|
|
x={x(p.decade)}
|
|
y={H - 6}
|
|
fontSize="10"
|
|
fill="#8b93a1"
|
|
textAnchor="middle"
|
|
>
|
|
{p.decade}s
|
|
</text>
|
|
</g>
|
|
))}
|
|
</svg>
|
|
);
|
|
}
|