ControlRL / app /src /content /embeds /performance-dashboard.html
seonglae-holistic's picture
feat: CRL interactive article with embeds and data
0af6f2b
Raw
History Blame Contribute Delete
13.1 kB
<div class="performance-dashboard"></div>
<style>
.performance-dashboard {
position: relative;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
width: 100%;
}
.performance-dashboard .chart-card {
background: var(--page-bg);
border: 1px solid var(--border-color);
border-radius: 12px;
padding: 24px 20px 16px;
}
.performance-dashboard svg { display: block; width: 100%; }
.performance-dashboard .axis path { stroke: none; }
.performance-dashboard .axis line { stroke: var(--axis-color); shape-rendering: crispEdges; }
.performance-dashboard .axis text { fill: var(--tick-color); font-size: 11px; }
.performance-dashboard .grid line { stroke: var(--grid-color); stroke-dasharray: 2, 2; }
.performance-dashboard .grid path { stroke: none; }
.performance-dashboard .bar-before { fill: #9CA3AF; }
.performance-dashboard .bar-after { fill: #4F46E5; }
.performance-dashboard .bar-before, .performance-dashboard .bar-after {
cursor: pointer; transition: opacity 0.15s ease;
}
.performance-dashboard .bar-before:hover, .performance-dashboard .bar-after:hover { opacity: 0.85; }
.performance-dashboard .task-label { fill: var(--text-color); font-size: 12px; font-weight: 600; }
.performance-dashboard .layer-pill { font-size: 9px; font-weight: 700; fill: var(--muted-color); }
.performance-dashboard .layer-pill-bg { fill: var(--surface-bg, #f3f4f6); stroke: var(--border-color); stroke-width: 0.5; }
.performance-dashboard .category-dot { r: 4; }
.performance-dashboard .gain-pill text { font-size: 10px; font-weight: 700; fill: #16a34a; }
.performance-dashboard .gain-pill rect { fill: #dcfce7; }
.performance-dashboard .header {
display: flex; align-items: center; justify-content: space-between;
flex-wrap: wrap; gap: 16px; margin-top: 12px; padding-top: 12px;
border-top: 1px solid var(--border-color);
}
.performance-dashboard .legend { display: flex; flex-direction: column; align-items: flex-start; gap: 6px; }
.performance-dashboard .legend-title { font-size: 12px; font-weight: 700; color: var(--text-color); }
.performance-dashboard .legend .items { display: flex; flex-wrap: wrap; gap: 8px 14px; }
.performance-dashboard .legend .item {
display: inline-flex; align-items: center; gap: 6px;
white-space: nowrap; font-size: 12px; color: var(--text-color);
}
.performance-dashboard .legend .swatch {
width: 14px; height: 14px; border-radius: 3px; border: 1px solid var(--border-color);
}
.performance-dashboard .categories { display: flex; flex-wrap: wrap; gap: 8px 14px; }
.performance-dashboard .categories .cat-item {
display: inline-flex; align-items: center; gap: 5px;
font-size: 11px; color: var(--muted-color);
}
.performance-dashboard .categories .cat-dot { width: 8px; height: 8px; border-radius: 50%; }
</style>
<script>
(function() {
var ensureD3 = function(cb) {
if (window.d3 && typeof window.d3.select === 'function') return cb();
var s = document.getElementById('d3-cdn-script');
if (!s) {
s = document.createElement('script');
s.id = 'd3-cdn-script';
s.src = 'https://cdn.jsdelivr.net/npm/d3@7/dist/d3.min.js';
document.head.appendChild(s);
}
s.addEventListener('load', function() { if (window.d3) cb(); }, { once: true });
};
var bootstrap = function() {
var candidates = Array.from(document.querySelectorAll('.performance-dashboard'))
.filter(function(el) { return !el.dataset.mounted; });
var container = candidates[candidates.length - 1];
if (!container) return;
container.dataset.mounted = 'true';
var data = [
{ task: "MMLU", type: "Knowledge", layer: 24, before: 52.06, after: 55.37, gain: 3.31 },
{ task: "MMLU-Pro", type: "Knowledge", layer: 25, before: 30.30, after: 30.49, gain: 0.19 },
{ task: "BBQ Ambig", type: "Bias", layer: 5, before: 60.17, after: 65.86, gain: 5.69 },
{ task: "BBQ Disambig", type: "Bias", layer: 5, before: 84.38, after: 84.85, gain: 0.47 },
{ task: "SimpleQA", type: "Knowledge", layer: 8, before: 3.78, after: 4.00, gain: 0.22 },
{ task: "GSM8K", type: "Reasoning", layer: 24, before: 54.62, after: 55.65, gain: 1.03 },
{ task: "HarmBench", type: "Safety", layer: 21, before: 41.46, after: 49.12, gain: 7.66 },
{ task: "XSTest", type: "Safety", layer: 12, before: 86.35, after: 87.62, gain: 1.27 }
];
var catColors = { Knowledge: '#8B5CF6', Bias: '#EC4899', Safety: '#F97316', Reasoning: '#06B6D4' };
var barColors = { before: '#9CA3AF', after: '#4F46E5' };
container.style.position = 'relative';
var tip = document.createElement('div');
Object.assign(tip.style, {
position:'absolute',top:'0',left:'0',transform:'translate(-9999px,-9999px)',
pointerEvents:'none',padding:'10px 14px',borderRadius:'10px',fontSize:'12px',lineHeight:'1.5',
border:'1px solid var(--border-color)',background:'var(--surface-bg)',color:'var(--text-color)',
boxShadow:'0 4px 24px rgba(0,0,0,.18)',opacity:'0',transition:'opacity .12s ease',
zIndex:'1000',maxWidth:'260px'
});
var tipInner = document.createElement('div');
tip.appendChild(tipInner);
container.appendChild(tip);
function showTip(event, d) {
tipInner.innerHTML =
'<div style="font-weight:700;margin-bottom:4px"><span style="display:inline-block;width:8px;height:8px;border-radius:50%;background:'+catColors[d.type]+';margin-right:6px;vertical-align:middle"></span>'+d.task+
'<span style="font-size:10px;color:var(--muted-color);margin-left:4px">'+d.type+' · L'+d.layer+'</span></div>'+
'<div style="display:flex;gap:16px;margin-top:2px"><div><span style="color:var(--muted-color)">Before:</span> <strong>'+d.before.toFixed(2)+'</strong></div>'+
'<div><span style="color:var(--muted-color)">After:</span> <strong style="color:#4F46E5">'+d.after.toFixed(2)+'</strong></div></div>'+
'<div style="margin-top:4px;color:#16a34a;font-weight:700">+'+d.gain.toFixed(2)+' gain</div>';
var coords = d3.pointer(event, container);
tip.style.transform = 'translate('+(coords[0]+14)+'px,'+(coords[1]-14)+'px)';
tip.style.opacity = '1';
}
function hideTip() { tip.style.opacity='0'; tip.style.transform='translate(-9999px,-9999px)'; }
var card = document.createElement('div');
card.className = 'chart-card';
container.appendChild(card);
var svg = d3.select(card).append('svg').style('display','block');
var gRoot = svg.append('g');
var gGrid = gRoot.append('g').attr('class','grid');
var gBars = gRoot.append('g');
var gLabels = gRoot.append('g');
var gGains = gRoot.append('g');
var gAxes = gRoot.append('g');
var margin = {top:16,right:80,bottom:36,left:140};
var hasAnimated = false;
function roundedBar(xPos, yPos, w, h, r) {
r = Math.min(r, h/2, w>0?w:0);
if(w<=0) return 'M'+xPos+','+yPos+'h0v'+h+'h0Z';
return 'M'+xPos+','+yPos+'h'+(w-r)+'a'+r+','+r+',0,0,1,'+r+','+r+'v'+(h-2*r)+'a'+r+','+r+',0,0,1,'+(-r)+','+r+'h'+(-(w-r))+'Z';
}
function render(animate) {
var width = container.clientWidth || 800;
var rowH = 52;
var height = margin.top + margin.bottom + data.length * rowH;
svg.attr('width',width).attr('height',height);
gRoot.attr('transform','translate('+margin.left+','+margin.top+')');
var innerW = width-margin.left-margin.right;
var innerH = height-margin.top-margin.bottom;
var maxVal = d3.max(data, function(d){return Math.max(d.before,d.after);});
var xMax = Math.ceil(maxVal/10)*10;
var y = d3.scaleBand().domain(data.map(function(d){return d.task})).range([0,innerH]).paddingInner(0.3).paddingOuter(0.15);
var yBar = d3.scaleBand().domain(['before','after']).range([0,y.bandwidth()]).padding(0.12);
var x = d3.scaleLinear().domain([0,xMax]).range([0,innerW]).nice();
gGrid.selectAll('.grid-x').data([0]).join('g').attr('class','grid grid-x')
.call(d3.axisBottom(x).tickSize(innerH).tickFormat('').ticks(6))
.call(function(g){g.select('.domain').remove()});
gAxes.selectAll('.x-axis').data([0]).join('g').attr('class','x-axis axis')
.attr('transform','translate(0,'+innerH+')')
.call(d3.axisBottom(x).ticks(6).tickSizeOuter(0));
var labelGroups = gLabels.selectAll('.task-row').data(data,function(d){return d.task})
.join('g').attr('class','task-row')
.attr('transform',function(d){return 'translate(0,'+(y(d.task)+y.bandwidth()/2)+')'});
labelGroups.selectAll('.category-dot').data(function(d){return[d]}).join('circle')
.attr('class','category-dot').attr('cx',-margin.left+14).attr('cy',0).attr('r',4)
.attr('fill',function(d){return catColors[d.type]});
labelGroups.selectAll('.task-label').data(function(d){return[d]}).join('text')
.attr('class','task-label').attr('x',-margin.left+26).attr('y',0).attr('dy','0.35em')
.attr('text-anchor','start').text(function(d){return d.task});
labelGroups.selectAll('.layer-pill-bg').data(function(d){return[d]}).join('rect')
.attr('class','layer-pill-bg').attr('x',-16).attr('y',-8).attr('width',28).attr('height',16).attr('rx',6);
labelGroups.selectAll('.layer-pill').data(function(d){return[d]}).join('text')
.attr('class','layer-pill').attr('x',-2).attr('y',0).attr('dy','0.35em').attr('text-anchor','middle')
.text(function(d){return'L'+d.layer});
var dur = animate ? 700 : 0;
gBars.selectAll('.bar-before').data(data,function(d){return d.task+'-b'}).join('path')
.attr('class','bar-before')
.attr('transform',function(d){return'translate(0,'+(y(d.task)+yBar('before'))+')'})
.on('mouseenter',function(e,d){showTip(e,d)}).on('mousemove',function(e,d){showTip(e,d)}).on('mouseleave',hideTip)
.transition().duration(dur).ease(d3.easeCubicOut)
.attrTween('d',function(d){var tw=x(d.before),bw=yBar.bandwidth();var p=this._pw||0;this._pw=tw;return function(t){return roundedBar(0,0,animate?d3.interpolateNumber(p,tw)(t):tw,bw,3)}});
gBars.selectAll('.bar-after').data(data,function(d){return d.task+'-a'}).join('path')
.attr('class','bar-after')
.attr('transform',function(d){return'translate(0,'+(y(d.task)+yBar('after'))+')'})
.on('mouseenter',function(e,d){showTip(e,d)}).on('mousemove',function(e,d){showTip(e,d)}).on('mouseleave',hideTip)
.transition().duration(dur).delay(function(d,i){return animate?i*60:0}).ease(d3.easeCubicOut)
.attrTween('d',function(d){var tw=x(d.after),bw=yBar.bandwidth();var p=this._pw||0;this._pw=tw;return function(t){return roundedBar(0,0,animate?d3.interpolateNumber(p,tw)(t):tw,bw,3)}});
var gainG = gGains.selectAll('.gain-pill').data(data,function(d){return d.task}).join('g').attr('class','gain-pill')
.attr('transform',function(d){return'translate('+(x(d.after)+8)+','+(y(d.task)+yBar('after')+yBar.bandwidth()/2)+')'});
gainG.selectAll('text').data(function(d){return[d]}).join('text').attr('dy','0.35em').attr('x',6).text(function(d){return'+'+d.gain.toFixed(2)});
gainG.each(function(d){
var g=d3.select(this);var txt=g.select('text').node();if(!txt)return;var bb=txt.getBBox();
g.selectAll('rect').data([d]).join('rect').attr('x',0).attr('y',-bb.height/2-3).attr('width',bb.width+12).attr('height',bb.height+6).attr('rx',8);
this.appendChild(txt);
});
}
var header = document.createElement('div');
header.className = 'header';
var legend = document.createElement('div');
legend.className = 'legend';
legend.innerHTML = '<div class="legend-title">Legend</div><div class="items"></div>';
var items = legend.querySelector('.items');
[{l:'Before CRL',c:barColors.before},{l:'After CRL',c:barColors.after}].forEach(function(e){
items.innerHTML += '<span class="item"><span class="swatch" style="background:'+e.c+'"></span><span>'+e.l+'</span></span>';
});
header.appendChild(legend);
var cats = document.createElement('div');
cats.className = 'categories';
[['Knowledge','#8B5CF6'],['Bias','#EC4899'],['Safety','#F97316'],['Reasoning','#06B6D4']].forEach(function(p){
cats.innerHTML += '<span class="cat-item"><span class="cat-dot" style="background:'+p[1]+'"></span><span>'+p[0]+'</span></span>';
});
header.appendChild(cats);
container.appendChild(header);
var obs = new IntersectionObserver(function(entries){
entries.forEach(function(entry){
if(entry.isIntersecting&&!hasAnimated){hasAnimated=true;render(true);obs.disconnect();}
});
},{threshold:0.15});
obs.observe(container);
render(false);
if(window.ResizeObserver){new ResizeObserver(function(){if(hasAnimated)render(false)}).observe(container);}
};
if(document.readyState==='loading') document.addEventListener('DOMContentLoaded',function(){ensureD3(bootstrap)},{once:true});
else ensureD3(bootstrap);
})();
</script>