rai-bench / src /components /GuardrailBarChart.tsx
rohanjaggi
fix: remove guardrails accuracy
2b01b44
Raw
History Blame Contribute Delete
8.06 kB
'use client'
import { useState, useMemo } from 'react'
import type { GuardrailData } from '@/lib/types'
import { guardrailCreatorColor } from '@/lib/utils'
import { LabLogo } from '@/components/LabLogo'
type Metric = 'recall' | 'precision' | 'f1'
const TABS: { key: Metric; label: string; yLabel: string }[] = [
{ key: 'recall', label: 'Recall', yLabel: 'Score (Higher is Better)' },
{ key: 'precision', label: 'Precision', yLabel: 'Score (Higher is Better)' },
{ key: 'f1', label: 'F1 Score', yLabel: 'Score (Higher is Better)' },
]
const ML = 80
const MR = 80
const MT = 14
const BAR_H = 300
const LBL_H = 150
const VW = 1100
const VH = MT + BAR_H + LBL_H
const CHART_W = VW - ML - MR
const GAP = 4
const GRID = [0, 0.25, 0.5, 0.75, 1.0]
export default function GuardrailBarChart({ guardrails }: { guardrails: GuardrailData[] }) {
const [metric, setMetric] = useState<Metric>('f1')
const [activeCreators, setCreators] = useState<Set<string>>(new Set())
const allCreators = useMemo(
() => [...new Set(guardrails.map(g => g.creator))].sort(),
[guardrails],
)
const toggleCreator = (c: string) =>
setCreators(prev => { const s = new Set(prev); s.has(c) ? s.delete(c) : s.add(c); return s })
const sorted = useMemo(() => {
let list = [...guardrails]
if (activeCreators.size) list = list.filter(g => activeCreators.has(g.creator))
list = list.filter(g => g[metric] !== null)
return list.sort((a, b) => (b[metric] ?? 0) - (a[metric] ?? 0))
}, [guardrails, metric, activeCreators])
const n = sorted.length
const bw = n > 0 ? (CHART_W - (n - 1) * GAP) / n : 0
const legendCreators = useMemo(
() => [...new Set(sorted.map(g => g.creator))].sort(),
[sorted],
)
const tab = TABS.find(t => t.key === metric)!
return (
<div>
{/* Filters row */}
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: '1rem', marginBottom: '1.25rem', flexWrap: 'wrap' }}>
{/* Creator chips */}
<div style={{ display: 'flex', alignItems: 'center', gap: 6, flexWrap: 'wrap' }}>
<span style={{ fontSize: 10, fontWeight: 700, letterSpacing: '0.06em', textTransform: 'uppercase' as const, color: 'var(--text-3)', marginRight: 2 }}>Provider</span>
{allCreators.map(c => {
const cc = guardrailCreatorColor(c)
const on = activeCreators.has(c)
return (
<button key={c} onClick={() => toggleCreator(c)} style={{
height: 30, padding: '0 10px',
border: `1.5px solid ${on ? cc : 'var(--border-1)'}`,
borderRadius: 6, fontSize: 11, fontFamily: 'inherit', fontWeight: 700,
color: on ? cc : 'var(--text-2)',
background: on ? `${cc}12` : 'var(--bg-1)',
cursor: 'pointer', whiteSpace: 'nowrap' as const,
display: 'inline-flex', alignItems: 'center', gap: 5,
}}>
<span style={{ display: 'inline-flex', alignItems: 'center', lineHeight: 0 }}>
<LabLogo creator={c} />
</span>
{c}
</button>
)
})}
</div>
{/* Metric tabs */}
<div style={{ display: 'inline-flex', border: '1.5px solid var(--border-1)', borderRadius: 7, overflow: 'hidden', flexShrink: 0 }}>
{TABS.map((t, i) => (
<button key={t.key} onClick={() => setMetric(t.key)} style={{
height: 30, padding: '0 13px',
borderLeft: i > 0 ? '1px solid var(--border-1)' : 'none',
outline: 'none', border: i > 0 ? '1px solid var(--border-1)' : 'none',
borderTop: 'none', borderRight: 'none', borderBottom: 'none',
fontSize: 10, fontFamily: 'inherit', fontWeight: 700,
letterSpacing: '0.05em', textTransform: 'uppercase' as const,
color: metric === t.key ? 'white' : 'var(--text-2)',
background: metric === t.key ? 'var(--accent)' : 'transparent',
cursor: 'pointer', whiteSpace: 'nowrap' as const,
transition: 'background 0.12s, color 0.12s',
}}>
{t.label}
</button>
))}
</div>
</div>
{/* Chart */}
<div style={{
background: 'var(--bg-1)',
border: '1.5px solid var(--border-1)',
borderRadius: 10,
padding: '1rem 1rem 0',
boxShadow: '0 1px 4px rgba(0,0,0,0.05)',
}}>
<svg
viewBox={`0 0 ${VW} ${VH}`}
style={{ width: '100%', height: 'auto', display: 'block' }}
aria-label={`Bar chart: ${tab.label}`}
>
{/* Grid lines + y-axis labels */}
{GRID.map(lvl => {
const y = MT + BAR_H - lvl * BAR_H
return (
<g key={lvl}>
<line
x1={ML} y1={y} x2={VW - MR} y2={y}
stroke={lvl === 0 ? 'var(--border-2)' : 'var(--border-0)'}
strokeWidth={lvl === 0 ? 1 : 0.8}
strokeDasharray={lvl === 0 || lvl === 1 ? undefined : '4 4'}
/>
<text
x={ML - 5} y={y + 3.5}
textAnchor="end"
fontSize={9}
fill="var(--text-3)"
fontFamily="inherit"
>
{`${Math.round(lvl * 100)}`}
</text>
</g>
)
})}
{/* Y-axis title */}
<text
x={11}
y={MT + BAR_H / 2}
textAnchor="middle"
fontSize={8.5}
fill="var(--text-3)"
fontFamily="inherit"
transform={`rotate(-90, 11, ${MT + BAR_H / 2})`}
>
{tab.yLabel}
</text>
{/* Bars */}
{sorted.map((g, i) => {
const val = g[metric]!
const barHeight = val * BAR_H
const x = ML + i * (bw + GAP)
const y = MT + BAR_H - barHeight
const cc = guardrailCreatorColor(g.creator)
return (
<g key={`${g.rank}-${metric}`}>
<rect
x={x} y={y}
width={bw} height={barHeight}
fill={cc} fillOpacity={0.88}
rx={2}
/>
{barHeight > 18 && (
<text
x={x + bw / 2} y={y - 4}
textAnchor="middle"
fontSize={8}
fill={cc}
fontFamily="inherit"
fontWeight="bold"
>
{`${(val * 100).toFixed(0)}%`}
</text>
)}
<text
x={x + bw / 2}
y={MT + BAR_H + 5}
transform={`rotate(-45, ${x + bw / 2}, ${MT + BAR_H + 5})`}
textAnchor="end"
fontSize={10}
fill="var(--text-1)"
fontFamily="inherit"
fontWeight="600"
>
{g.guardrail}
</text>
</g>
)
})}
</svg>
{/* Legend */}
<div style={{
display: 'flex',
flexWrap: 'wrap',
gap: '8px 20px',
justifyContent: 'center',
padding: '12px 8px',
borderTop: '1px solid var(--border-0)',
marginTop: 4,
}}>
{legendCreators.map(c => (
<div key={c} style={{ display: 'flex', alignItems: 'center', gap: 6, fontSize: 12, color: 'var(--text-2)', fontWeight: 600 }}>
<div style={{ width: 11, height: 11, borderRadius: 2, background: guardrailCreatorColor(c), flexShrink: 0 }} />
{c}
</div>
))}
</div>
</div>
</div>
)
}