// ===== charts.jsx =====
// Custom SVG charts: Gauge, Radar, ModelBars, Hero data viz.
const { useState: useS, useEffect: useE, useRef: useR } = React;
// ---------- Gauge (large circular AIGC %) ----------
function Gauge({ value = 67, size = 240, label = 'AIGC 概率', sublabel = '', strokeWidth = 18 }) {
const r = (size - strokeWidth) / 2;
const c = 2 * Math.PI * r;
const [drawn, setDrawn] = useS(0);
useE(() => {
let raf, start;
const dur = 1400;
const step = (ts) => {
if (!start) start = ts;
const t = Math.min(1, (ts - start) / dur);
const e = 1 - Math.pow(1 - t, 3);
setDrawn(value * e);
if (t < 1) raf = requestAnimationFrame(step);
};
raf = requestAnimationFrame(step);
return () => cancelAnimationFrame(raf);
}, [value]);
// color stops by value
const color = drawn < 30 ? '#10B981' : drawn < 55 ? '#F59E0B' : drawn < 75 ? '#EF4444' : '#DC2626';
const trackColor = '#E2E8F0';
// 270° sweep from -135° to +135° (leave 90° gap at bottom)
const SWEEP = 0.78; // 280deg portion of full circle
const offset = c * (1 - (drawn / 100) * SWEEP);
return (
{label}
{Math.round(drawn)}%
{sublabel &&
{sublabel}
}
);
}
// ---------- Radar (5-axis polygon) ----------
function Radar({ dims, size = 360 }) {
const cx = size / 2, cy = size / 2;
const radius = size * 0.36;
const axes = MOCK.DIMENSIONS;
const N = axes.length;
const [t, setT] = useS(0);
useE(() => {
let raf, start;
const dur = 1200;
const step = (ts) => {
if (!start) start = ts;
const p = Math.min(1, (ts - start) / dur);
setT(1 - Math.pow(1 - p, 3));
if (p < 1) raf = requestAnimationFrame(step);
};
raf = requestAnimationFrame(step);
return () => cancelAnimationFrame(raf);
}, []);
const pt = (i, v) => {
const angle = (-Math.PI / 2) + (i * 2 * Math.PI / N);
const r = radius * (v / 100);
return [cx + Math.cos(angle) * r, cy + Math.sin(angle) * r];
};
const labelPt = (i) => {
const angle = (-Math.PI / 2) + (i * 2 * Math.PI / N);
const r = radius + 26;
return [cx + Math.cos(angle) * r, cy + Math.sin(angle) * r, angle];
};
const poly = axes.map((a, i) => pt(i, dims[a.key] * t).join(',')).join(' ');
return (
);
}
// ---------- Model match bars ----------
function ModelBars({ models }) {
return (
{models.map((m, i) => {
const intense = m.match >= 60;
const mid = m.match >= 35 && m.match < 60;
const barColor = intense ? '#EF4444' : mid ? '#F59E0B' : '#10B981';
return (
);
})}
);
}
// ---------- Hero data viz (decorative SVG with a 'live' probability curve) ----------
function HeroViz() {
const [phase, setPhase] = useS(0);
useE(() => {
const id = setInterval(() => setPhase(p => (p + 1) % 1000), 50);
return () => clearInterval(id);
}, []);
// Generate probability density curve
const points = [];
for (let i = 0; i <= 60; i++) {
const x = i / 60;
// Mixed bimodal: human (low) + AI (high)
const human = 0.42 * Math.exp(-Math.pow((x - 0.28) / 0.13, 2));
const ai = 0.58 * Math.exp(-Math.pow((x - 0.72) / 0.10, 2));
points.push({ x, y: human + ai, human, ai });
}
const W = 520, H = 360;
const toX = (x) => 40 + x * (W - 80);
const toY = (y) => H - 60 - y * (H - 100);
const pathFor = (key) => points.map((p, i) => `${i === 0 ? 'M' : 'L'} ${toX(p.x).toFixed(1)} ${toY(p[key]).toFixed(1)}`).join(' ');
const areaFor = (key, color) => {
const top = points.map((p, i) => `${i === 0 ? 'M' : 'L'} ${toX(p.x).toFixed(1)} ${toY(p[key]).toFixed(1)}`).join(' ');
return `${top} L ${toX(1).toFixed(1)} ${toY(0).toFixed(1)} L ${toX(0).toFixed(1)} ${toY(0).toFixed(1)} Z`;
};
// Animated scan ratio
const scanX = 0.05 + (phase % 200) / 200 * 0.9;
const localProb = points.reduce((acc, p) => acc + Math.exp(-Math.pow((p.x - scanX) / 0.05, 2)) * p.y, 0) / 8;
return (
实时统计示意
);
}
// ---------- Process step indicator ----------
function StepNumber({ n }) {
return (
{n}
);
}
Object.assign(window, { Gauge, Radar, ModelBars, HeroViz, StepNumber });