| "use client"; |
|
|
| import { motion } from "framer-motion"; |
| import type { CDCTPoint } from "@/lib/types"; |
|
|
| export function CompressionChart({ data }: { data: CDCTPoint[] }) { |
| if (!data.length) return null; |
|
|
| const w = 400, h = 180, pad = 30; |
| const plotW = w - pad * 2, plotH = h - pad * 2; |
|
|
| const points = data.map((d, i) => ({ |
| x: pad + (i / Math.max(data.length - 1, 1)) * plotW, |
| sa: pad + plotH - (d.sa_score / 10) * plotH, |
| label: `${(d.compression_level * 100).toFixed(0)}%`, |
| })); |
|
|
| const linePath = points.map((p) => `${p.x},${p.sa}`).join(" "); |
|
|
| return ( |
| <div className="w-full"> |
| <svg viewBox={`0 0 ${w} ${h}`} className="w-full overflow-visible"> |
| {/* Minimal Grid */} |
| {[0, 5, 10].map((v) => { |
| const y = pad + plotH - (v / 10) * plotH; |
| return ( |
| <line key={v} x1={pad} y1={y} x2={w - pad} y2={y} stroke="rgba(255,255,255,0.03)" strokeWidth="0.5" /> |
| ); |
| })} |
| |
| {/* Signal Line */} |
| <motion.polyline |
| initial={{ pathLength: 0 }} |
| animate={{ pathLength: 1 }} |
| transition={{ duration: 1.5, ease: [0.4, 0, 0.2, 1] }} |
| points={linePath} |
| fill="none" |
| stroke="rgba(255, 255, 255, 0.3)" |
| strokeWidth="1.5" |
| strokeLinecap="round" |
| strokeLinejoin="round" |
| /> |
| |
| {/* Nodes */} |
| {points.map((p, i) => ( |
| <circle key={i} cx={p.x} cy={p.sa} r="2" fill="white" /> |
| ))} |
| |
| {/* X Labels */} |
| {points.map((p, i) => ( |
| <text key={i} x={p.x} y={h - 10} textAnchor="middle" fill="var(--text-dim)" fontSize="7" fontWeight="500" className="opacity-40"> |
| {p.label} |
| </text> |
| ))} |
| </svg> |
| <div className="flex justify-center mt-2"> |
| <span className="text-[9px] uppercase tracking-widest text-text-dim">Semantic Integrity Curve</span> |
| </div> |
| </div> |
| ); |
| } |
|
|