Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
1a9bcb3
Added link to paper in nav bar
shenrunzhang Nov 5, 2025
48992c8
updated instructions for submitting to just go to project page and fo…
shenrunzhang Nov 10, 2025
98fabee
Made text on hero section lighter, rephrased cta button 'get started'…
shenrunzhang Nov 10, 2025
3dbff83
Made subtitles one liner
shenrunzhang Nov 10, 2025
e869bd1
Added logo and rescaled 0-100
shenrunzhang Nov 10, 2025
ae04aad
fixed metrics range, and dangling word
shenrunzhang Nov 10, 2025
b18f37f
Changed commercial/academic -> closed/open source. Added Chayan to le…
shenrunzhang Nov 18, 2025
ee68aaf
Fixed col width
shenrunzhang Nov 18, 2025
501520f
changed vllm affiliation to vllm sr team
shenrunzhang Nov 18, 2025
a9c0292
Fixed premature scrollbar on loaderboard
shenrunzhang Nov 18, 2025
9d1024f
added text wrapping to leaderboard affiliations
shenrunzhang Nov 18, 2025
7633325
Added two new metrics accuracy and cost/1k query
shenrunzhang Nov 18, 2025
111258a
Added eval metric explanation
shenrunzhang Nov 18, 2025
2229a48
spelling
shenrunzhang Nov 18, 2025
76628a1
Added website links to all routers and removed dangling word from lea…
shenrunzhang Nov 19, 2025
a85815c
Added beta slider
shenrunzhang Nov 20, 2025
307c9c0
Fixed sizing for mobile screens in homepage and leaderboard page and …
shenrunzhang Nov 20, 2025
05640f2
Fixed homepage padding for phone screens
shenrunzhang Nov 20, 2025
db8b60b
changed get started button color
shenrunzhang Nov 20, 2025
71c1d03
Fixed beta slider
shenrunzhang Nov 20, 2025
4e98b10
changed get started button color
shenrunzhang Nov 21, 2025
425fde2
Migrated data from mockdata.ts to /src/data/
shenrunzhang Dec 1, 2025
c14626f
Changed website title for better indexing
shenrunzhang Dec 10, 2025
cfd987e
Added HF link
shenrunzhang Dec 12, 2025
7d29235
Added hf link
shenrunzhang Dec 12, 2025
0a9d741
second compare modal draft
shenrunzhang Dec 12, 2025
002ec7c
Changed website link icon
shenrunzhang Dec 12, 2025
c499ec8
removed router type column
shenrunzhang Dec 12, 2025
c2d6e03
Reduced horizontal space between tabs in nav bar
shenrunzhang Dec 13, 2025
3dda5d6
Added hf link to nav
shenrunzhang Dec 15, 2025
223f6e0
Added compare modal with real data
shenrunzhang Dec 15, 2025
39f706b
Merge branch 'main' into feature/router_comparison
shenrunzhang Dec 15, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
222 changes: 222 additions & 0 deletions src/components/CompareDeferralChart.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,222 @@
import React, { useMemo, useCallback } from 'react';
import {
ScatterChart,
Scatter,
CartesianGrid,
XAxis,
YAxis,
Tooltip,
ResponsiveContainer,
LabelList,
} from 'recharts';
import { CompareMetric } from '../data/routerData';

interface CompareDeferralChartProps {
selectedPoints: ScatterPoint[];
backgroundPoints: ScatterPoint[];
metric: CompareMetric;
contextLabel?: string;
height?: number;
}

type ScatterPoint = {
routerId: string;
routerName: string;
metricValue: number;
costPer1k: number;
color: string;
};

const formatCurrency = (value: number) => `$${value.toFixed(value >= 10 ? 0 : 2)}`;

const logTicks = (min: number, max: number) => {
const ticks: number[] = [];
const minLog = Math.floor(Math.log10(min));
const maxLog = Math.ceil(Math.log10(max));

for (let i = minLog; i <= maxLog; i++) {
const tickValue = Math.pow(10, i);
if (tickValue >= min && tickValue <= max) {
ticks.push(Number(tickValue.toFixed(3)));
}
}

return ticks;
};

const metricLabels: Record<CompareMetric, string> = {
accuracy: 'Accuracy (%)',
robustness: 'Robustness',
cost: 'Cost Score',
};

const CompareDeferralChart: React.FC<CompareDeferralChartProps> = ({
selectedPoints,
backgroundPoints,
metric,
contextLabel,
height = 320,
}) => {
const combinedPoints = useMemo(
() => [...backgroundPoints, ...selectedPoints],
[backgroundPoints, selectedPoints]
);

const costDomain = useMemo(() => {
if (!combinedPoints.length) return { min: 0.01, max: 100 };
const minValue = Math.min(...combinedPoints.map(point => point.costPer1k));
const maxValue = Math.max(...combinedPoints.map(point => point.costPer1k));
return {
min: Math.max(0.01, minValue * 0.7),
max: maxValue * 1.3,
};
}, [combinedPoints]);

const metricDomain = useMemo(() => {
if (!combinedPoints.length) return { min: 20, max: 100 };
const minValue = Math.min(...combinedPoints.map(point => point.metricValue));
const maxValue = Math.max(...combinedPoints.map(point => point.metricValue));
const padding = Math.max(4, (maxValue - minValue) * 0.08);
return {
min: Math.max(0, minValue - padding),
max: Math.min(100, maxValue + padding),
};
}, [combinedPoints]);

const costTicks = logTicks(costDomain.min, costDomain.max);
const metricLabel = metricLabels[metric];
const formatMetricValue = useCallback(
(value: number) => (metric === 'accuracy' ? `${value.toFixed(1)}%` : value.toFixed(1)),
[metric]
);
const renderTooltip = useCallback(
(props: any) => {
const { active, payload } = props;
if (!active || !payload?.length) return null;
const point = payload[0].payload as ScatterPoint;
return (
<div className="deferral-tooltip">
<p className="deferral-tooltip-name">{point.routerName}</p>
<p>
{metricLabel}: <strong>{formatMetricValue(point.metricValue)}</strong>
</p>
<p>
Cost per 1k: <strong>{formatCurrency(point.costPer1k)}</strong>
</p>
</div>
);
},
[metricLabel, formatMetricValue]
);

if (!selectedPoints.length && !backgroundPoints.length) {
return (
<div className="deferral-chart-panel empty">
<p>Select routers to plot on the deferral curve.</p>
</div>
);
}

return (
<div className="deferral-chart-panel">
<div className="panel-header">
<div>
<p className="panel-eyebrow">Efficiency view</p>
<h4 className="panel-title">Deferral curve</h4>
{contextLabel && <p className="panel-context">{contextLabel}</p>}
</div>
</div>

<ResponsiveContainer width="100%" height={height}>
<ScatterChart margin={{ top: 10, right: 20, bottom: 40, left: 60 }}>
<CartesianGrid stroke="#e2e8f0" strokeDasharray="3 3" />
<XAxis
type="number"
dataKey="costPer1k"
name="Cost per 1k"
scale="log"
domain={[costDomain.min, costDomain.max]}
ticks={costTicks}
tickFormatter={formatCurrency}
stroke="#475569"
label={{
value: 'Cost per 1k tokens (log scale)',
position: 'insideBottom',
offset: -5,
fill: '#0f172a',
}}
/>
<YAxis
type="number"
dataKey="metricValue"
name={metricLabel}
domain={[metricDomain.min, metricDomain.max]}
tickFormatter={formatMetricValue}
stroke="#475569"
label={{
value: metricLabel,
angle: -90,
position: 'insideLeft',
offset: -10,
fill: '#0f172a',
}}
/>
<Tooltip content={renderTooltip} cursor={{ stroke: '#e2e8f0', strokeWidth: 1 }} />
{backgroundPoints.length > 0 && (
<Scatter
data={backgroundPoints}
fill="#c7cdd8"
stroke="#94a3b8"
shape="circle"
legendType="none"
isAnimationActive={false}
opacity={0.7}
/>
)}
{selectedPoints.map(point => (
<Scatter
key={point.routerId}
name={point.routerName}
data={[point]}
fill={point.color}
shape="circle"
legendType="circle"
isAnimationActive={false}
>
<LabelList
dataKey="metricValue"
content={props => {
const rawX = props.x;
const rawY = props.y;
const x =
typeof rawX === 'number' ? rawX : typeof rawX === 'string' ? Number(rawX) : NaN;
const y =
typeof rawY === 'number' ? rawY : typeof rawY === 'string' ? Number(rawY) : NaN;
if (!Number.isFinite(x) || !Number.isFinite(y)) return null;
return (
<text
x={x + 8}
y={y - 10}
fill="#0f172a"
fontSize={12}
fontWeight={600}
paintOrder="stroke"
stroke="#ffffff"
strokeWidth={3}
strokeLinejoin="round"
strokeLinecap="round"
>
{point.routerName}
</text>
);
}}
/>
</Scatter>
))}
</ScatterChart>
</ResponsiveContainer>
</div>
);
};

export default CompareDeferralChart;
Loading