"use client"; import { useId, useMemo, useState } from "react"; import { hasBench, ms, score, usd, type ModelRow } from "./format"; interface Pt { m: ModelRow; x: number; y: number } /** Inline-SVG scatter: x on a log scale (latency or cost), y = mean deterministic score. Colour = provider (accent / ai), legend + hover tooltip, labelled highlights. */ export function Scatter({ models, metric, highlight = [] }: { models: ModelRow[]; metric: "latency" | "cost"; highlight?: string[] }) { const id = useId(); const [hover, setHover] = useState(null); const W = 560, H = 310, P = { l: 44, r: 16, t: 44, b: 36 }; const pts = useMemo(() => models.filter(hasBench).map((m) => ({ m, x: metric === "latency" ? m.benchmark!.total_median_ms ?? 0 : m.benchmark!.cost_mean ?? 0, y: m.benchmark!.mean_score ?? 0 })).filter((p) => p.x > 0), [models, metric]); if (!pts.length) return
No benchmark data.
; const xs = pts.map((p) => Math.log10(p.x)); const xmin = Math.floor(Math.min(...xs) * 2) / 2, xmax = Math.ceil(Math.max(...xs) * 2) / 2; const ymin = Math.min(0.9, Math.floor(Math.min(...pts.map((p) => p.y)) * 50) / 50), ymax = 1.0; const X = (v: number) => P.l + ((Math.log10(v) - xmin) / (xmax - xmin)) * (W - P.l - P.r); const Y = (v: number) => P.t + (1 - (v - ymin) / (ymax - ymin)) * (H - P.t - P.b); const xticks: number[] = []; for (let e = Math.floor(xmin); e <= Math.ceil(xmax); e++) for (const k of [1, 2, 5]) { const v = k * 10 ** e; if (Math.log10(v) >= xmin - 1e-9 && Math.log10(v) <= xmax + 1e-9) xticks.push(v); } const yticks = Array.from({ length: 6 }, (_, i) => ymin + ((ymax - ymin) * i) / 5); const fmtX = (v: number) => (metric === "latency" ? ms(v) : usd(v)); const col = (p: string) => (p === "openai" ? "var(--accent)" : "var(--ai)"); const labelled = new Set(highlight); // staggered label tiers for highlighted points (sorted by x) so labels on the same score line do not collide // greedy placement: a label takes the lowest tier whose previous label ends before it starts; no free tier → no label (tooltip still carries it) const tiers = new Map(); const right = [-Infinity, -Infinity, -Infinity]; pts.filter((p) => labelled.has(p.m.id)).sort((a, b) => a.x - b.x).forEach((p) => { const w = p.m.id.length * 5.6 + 8; const left = X(p.x) - w / 2; const t = right.findIndex((r) => r < left); if (t >= 0) { tiers.set(p.m.id, t); right[t] = left + w; } }); return (
{metric === "latency" ? "Median latency vs mean score" : "Cost per run vs mean score"} {yticks.map((t) => {t.toFixed(2)})} {xticks.map((t) => {fmtX(t)})} {metric === "latency" ? "Median latency (log)" : "Estimated $ per run (log)"} Mean score {pts.map((p) => {tiers.has(p.m.id) && {p.m.id}} setHover(p)} onMouseLeave={() => setHover(null)} onFocus={() => setHover(p)} onBlur={() => setHover(null)} style={{ cursor: "default", outline: "none" }} /> )}
OpenAIAnthropicLabels = routed primaries
{hover &&
{hover.m.id}
Score
{score(hover.y)}
{metric === "latency" ? "Latency" : "$/run"}
{fmtX(hover.x)}
Judged
{score(hover.m.benchmark?.judged_mean)}
TTFT
{ms(hover.m.benchmark?.ttft_median_ms)}
{metric === "latency" ? "$/run" : "Latency"}
{metric === "latency" ? usd(hover.m.benchmark?.cost_mean) : ms(hover.m.benchmark?.total_median_ms)}
}
); }