|
|
<div class="d3-pie-quad"></div> |
|
|
<style> |
|
|
|
|
|
.d3-pie-quad { container-type: inline-size; } |
|
|
.d3-pie-quad .legend { width: 80%;margin: 0 auto; font-size: 12px; line-height: 1.35; color: var(--text-color); } |
|
|
.d3-pie-quad .legend { margin-bottom: 32px; } |
|
|
.d3-pie-quad .legend .items { display:flex; flex-wrap:wrap; gap:8px 14px; align-items:center; justify-content:center; } |
|
|
.d3-pie-quad .legend .item { display:flex; align-items:center; gap:8px; white-space:nowrap; } |
|
|
.d3-pie-quad .legend .swatch { width:14px; height:14px; border-radius:3px; display:inline-block; border: 1px solid var(--border-color); } |
|
|
.d3-pie-quad .legend .title { display:block; text-align:center; font-weight:800; margin-bottom:6px; } |
|
|
.d3-pie-quad .caption { font-size: 14px; font-weight: 800; fill: var(--text-color); } |
|
|
.d3-pie-quad .caption-subtitle { font-size: 11px; font-weight: 400; fill: var(--muted-color); } |
|
|
.d3-pie-quad .nodata { font-size: 12px; fill: var(--muted-color); } |
|
|
|
|
|
.d3-pie-quad.hovering .legend .item.ghost { opacity: .35; } |
|
|
.d3-pie-quad .slice-label { font-size: 11px; font-weight: 700; fill: var(--text-color); paint-order: stroke; stroke: var(--transparent-page-contrast); stroke-width: 3px; } |
|
|
|
|
|
.d3-pie-quad .slice { |
|
|
transition: opacity .15s ease; |
|
|
} |
|
|
.d3-pie-quad.hovering .slice.ghost { |
|
|
opacity: .25; |
|
|
} |
|
|
|
|
|
.d3-pie-quad .plots-grid { |
|
|
display: flex; |
|
|
flex-wrap: wrap; |
|
|
justify-content: center; |
|
|
align-items: flex-start; |
|
|
gap: 12px 20px; |
|
|
margin-top: 4px; |
|
|
margin-left: auto; |
|
|
margin-right: auto; |
|
|
width: 100%; |
|
|
} |
|
|
|
|
|
.content-grid .d3-pie-quad .plots-grid { width: 100%; } |
|
|
.content-grid .d3-pie-quad .pie-cell { flex: 0 0 calc((100% - 20px)/2); } |
|
|
|
|
|
.wide .d3-pie-quad .plots-grid, |
|
|
.full-width .d3-pie-quad .plots-grid { width: 100%; } |
|
|
.wide .d3-pie-quad .pie-cell, |
|
|
.full-width .d3-pie-quad .pie-cell { flex: 0 0 calc((100% - 60px)/4); } |
|
|
|
|
|
.content-grid .d3-pie-quad .plots-grid { width: min(740px, 100%); } |
|
|
.d3-pie-quad .pie-cell { |
|
|
display: flex; |
|
|
flex-direction: column; |
|
|
align-items: center; |
|
|
flex: 0 0 360px; |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@media (max-width: 500px) { |
|
|
.d3-pie-quad .pie-cell { flex: 0 0 100%; } |
|
|
} |
|
|
|
|
|
.d3-pie-quad .d3-tooltip { |
|
|
z-index: var(--z-elevated); |
|
|
backdrop-filter: saturate(1.12) blur(8px); |
|
|
} |
|
|
.d3-pie-quad .d3-tooltip__inner { |
|
|
display: flex; |
|
|
flex-direction: column; |
|
|
gap: 6px; |
|
|
min-width: 220px; |
|
|
} |
|
|
.d3-pie-quad .d3-tooltip__inner > div:first-child { |
|
|
font-weight: 800; |
|
|
letter-spacing: 0.1px; |
|
|
margin-bottom: 0; |
|
|
} |
|
|
.d3-pie-quad .d3-tooltip__inner > div:nth-child(2) { |
|
|
font-size: 11px; |
|
|
color: var(--muted-color); |
|
|
display: block; |
|
|
margin-top: -4px; |
|
|
margin-bottom: 2px; |
|
|
letter-spacing: 0.1px; |
|
|
} |
|
|
.d3-pie-quad .d3-tooltip__inner > div:nth-child(n+3) { |
|
|
padding-top: 6px; |
|
|
border-top: 1px solid var(--border-color); |
|
|
} |
|
|
.d3-pie-quad .d3-tooltip__color-dot { |
|
|
display: inline-block; |
|
|
width: 12px; |
|
|
height: 12px; |
|
|
border-radius: 3px; |
|
|
border: 1px solid var(--border-color); |
|
|
} |
|
|
</style> |
|
|
<script> |
|
|
(() => { |
|
|
const THIS_SCRIPT = document.currentScript; |
|
|
const ensureD3 = (cb) => { |
|
|
if (window.d3 && typeof window.d3.select === 'function') return cb(); |
|
|
let 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); } |
|
|
const onReady = () => { if (window.d3 && typeof window.d3.select === 'function') cb(); }; |
|
|
s.addEventListener('load', onReady, { once: true }); |
|
|
if (window.d3) onReady(); |
|
|
}; |
|
|
|
|
|
const bootstrap = () => { |
|
|
const scriptEl = THIS_SCRIPT; |
|
|
const host = scriptEl && scriptEl.parentElement; |
|
|
let container = null; |
|
|
if (host && host.querySelector) { |
|
|
container = host.querySelector('.d3-pie-quad'); |
|
|
} |
|
|
if (!container) { |
|
|
let sib = scriptEl && scriptEl.previousElementSibling; |
|
|
while (sib && !(sib.classList && sib.classList.contains('d3-pie-quad'))) { |
|
|
sib = sib.previousElementSibling; |
|
|
} |
|
|
container = sib || document.querySelector('.d3-pie-quad'); |
|
|
} |
|
|
if (!container) return; |
|
|
if (container.dataset) { if (container.dataset.mounted === 'true') return; container.dataset.mounted = 'true'; } |
|
|
|
|
|
|
|
|
container.style.position = container.style.position || 'relative'; |
|
|
let tip = container.querySelector('.d3-tooltip'); let tipInner; |
|
|
if (!tip) { |
|
|
tip = document.createElement('div'); tip.className = 'd3-tooltip'; |
|
|
Object.assign(tip.style, { |
|
|
position:'absolute', top:'0px', left:'0px', transform:'translate(-9999px, -9999px)', pointerEvents:'none', |
|
|
padding:'10px 12px', borderRadius:'12px', fontSize:'12px', lineHeight:'1.35', border:'1px solid var(--border-color)', |
|
|
background:'var(--surface-bg)', color:'var(--text-color)', boxShadow:'0 8px 32px rgba(0,0,0,.28), 0 2px 8px rgba(0,0,0,.12)', opacity:'0', transition:'opacity .12s ease', |
|
|
zIndex: 'var(--z-elevated)', backdropFilter: 'saturate(1.12) blur(8px)' |
|
|
}); |
|
|
tipInner = document.createElement('div'); tipInner.className = 'd3-tooltip__inner'; tipInner.style.textAlign='left'; tip.appendChild(tipInner); container.appendChild(tip); |
|
|
} else { tipInner = tip.querySelector('.d3-tooltip__inner') || tip; } |
|
|
|
|
|
|
|
|
const legendHost = document.createElement('div'); legendHost.className = 'legend'; container.appendChild(legendHost); |
|
|
const plotsHost = document.createElement('div'); plotsHost.className = 'plots-grid'; container.appendChild(plotsHost); |
|
|
|
|
|
|
|
|
const METRICS = [ |
|
|
{ key:'answer_total_tokens', name:'Answer Tokens', title:'Weighted by ', letter:'a' }, |
|
|
{ key:'total_samples', name:'Number of Samples', title:'Weighted by ', letter:'b' }, |
|
|
{ key:'total_turns', name:'Number of Turns', title:'Weighted by ', letter:'c' }, |
|
|
{ key:'total_images', name:'Number of Images', title:'Weighted by ', letter:'d' } |
|
|
]; |
|
|
|
|
|
|
|
|
const CSV_PATHS = [ |
|
|
'/data/vision.csv' |
|
|
]; |
|
|
|
|
|
const fetchFirstAvailable = async (paths) => { |
|
|
for (const p of paths) { |
|
|
try { |
|
|
const res = await fetch(p, { cache: 'no-cache' }); |
|
|
if (res.ok) { return await res.text(); } |
|
|
} catch (_) { } |
|
|
} |
|
|
throw new Error('CSV not found: vision.csv'); |
|
|
}; |
|
|
|
|
|
const parseCsv = (text) => d3.csvParse(text, (d) => ({ |
|
|
subset_name: (d['subset_name']||'').trim(), |
|
|
eagle_cathegory: (d['eagle_cathegory']||'').trim(), |
|
|
answer_total_tokens: +((d['answer_total_tokens']||'0').toString().trim()) || 0, |
|
|
total_samples: +((d['total_samples']||'0').toString().trim()) || 0, |
|
|
total_turns: +((d['total_turns']||'0').toString().trim()) || 0, |
|
|
total_images: +((d['total_images']||'0').toString().trim()) || 0 |
|
|
})); |
|
|
|
|
|
|
|
|
let width=800; const margin = { top: 8, right: 24, bottom: 0, left: 24 }; |
|
|
const CAPTION_GAP = 36; |
|
|
const GAP_X = 20; |
|
|
const GAP_Y = 12; |
|
|
const TOP_OFFSET = 4; |
|
|
const DONUT_INNER_RATIO = 0.58; |
|
|
|
|
|
const SVG_VPAD = 16; |
|
|
|
|
|
const updateSize = () => { |
|
|
width = container.clientWidth || 800; |
|
|
return { innerWidth: width - margin.left - margin.right }; |
|
|
}; |
|
|
|
|
|
function renderLegend(categories, colorOf){ |
|
|
legendHost.style.display = 'flex'; |
|
|
legendHost.style.alignItems = 'center'; |
|
|
legendHost.style.justifyContent = 'center'; |
|
|
legendHost.innerHTML = `<div class="items">${categories.map(c => `<div class="item" data-category="${c}"><span class=\"swatch\" style=\"background:${colorOf(c)}\"></span><span style=\"font-weight:500\">${c}</span></div>`).join('')}</div>`; |
|
|
} |
|
|
|
|
|
function drawPies(rows){ |
|
|
const { innerWidth } = updateSize(); |
|
|
|
|
|
|
|
|
const categories = Array.from(new Set(rows.map(r => r.eagle_cathegory || 'Unknown'))).sort(); |
|
|
const getCatColors = (n) => { |
|
|
try { if (window.ColorPalettes && typeof window.ColorPalettes.getColors === 'function') return window.ColorPalettes.getColors('categorical', n); } catch(_) {} |
|
|
return (d3.schemeTableau10 ? d3.schemeTableau10.slice(0, n) : ['#4e79a7','#f28e2b','#e15759','#76b7b2','#59a14f','#edc948','#b07aa1','#ff9da7','#9c755f','#bab0ab'].slice(0, n)); |
|
|
}; |
|
|
const color = d3.scaleOrdinal().domain(categories).range(getCatColors(categories.length)); |
|
|
const colorOf = (cat) => color(cat || 'Unknown'); |
|
|
|
|
|
|
|
|
plotsHost.innerHTML = ''; |
|
|
|
|
|
|
|
|
renderLegend(categories, colorOf); |
|
|
|
|
|
|
|
|
const CELL_BASIS = 360; |
|
|
const radius = Math.max(80, Math.min(120, Math.floor(CELL_BASIS * 0.42))); |
|
|
const innerR = Math.round(radius * DONUT_INNER_RATIO); |
|
|
|
|
|
plotsHost.style.position = 'relative'; |
|
|
plotsHost.style.marginTop = (TOP_OFFSET) + 'px'; |
|
|
|
|
|
const pie = d3.pie().sort(null).value(d => d.value).padAngle(0.005); |
|
|
const arc = d3.arc().innerRadius(innerR).outerRadius(radius).cornerRadius(3); |
|
|
const arcLabel = d3.arc().innerRadius((innerR + radius) / 2).outerRadius((innerR + radius) / 2); |
|
|
|
|
|
|
|
|
|
|
|
METRICS.forEach((metric, idx) => { |
|
|
|
|
|
const totals = new Map(); categories.forEach(c => totals.set(c, 0)); |
|
|
rows.forEach(r => { totals.set(r.eagle_cathegory, totals.get(r.eagle_cathegory) + (r[metric.key] || 0)); }); |
|
|
const values = categories.map(c => ({ category: c, value: totals.get(c) || 0 })); |
|
|
const nonZeroValues = values.filter(v => (v.value || 0) > 0); |
|
|
const totalSum = d3.sum(nonZeroValues, d => d.value); |
|
|
|
|
|
|
|
|
const cell = document.createElement('div'); |
|
|
cell.className = 'pie-cell'; |
|
|
cell.style.width = (radius * 2) + 'px'; |
|
|
cell.style.height = (radius * 2 + SVG_VPAD * 2 + CAPTION_GAP + 24) + 'px'; |
|
|
cell.style.display = 'flex'; |
|
|
cell.style.flexDirection = 'column'; |
|
|
cell.style.alignItems = 'center'; |
|
|
cell.style.justifyContent = 'flex-start'; |
|
|
plotsHost.appendChild(cell); |
|
|
|
|
|
|
|
|
const svg = d3.select(cell).append('svg').attr('width', radius * 2).attr('height', radius * 2 + SVG_VPAD * 2).style('display','block'); |
|
|
const gCell = svg.append('g').attr('transform', `translate(${radius},${radius + SVG_VPAD})`); |
|
|
|
|
|
if (!totalSum || totalSum <= 0 || nonZeroValues.length === 0) { |
|
|
gCell.append('text').attr('class','nodata').attr('text-anchor','middle').attr('dy','0').text('No data for this metric'); |
|
|
} else { |
|
|
const data = pie(nonZeroValues); |
|
|
const percent = (v) => (v / totalSum) * 100; |
|
|
|
|
|
|
|
|
const slices = gCell.selectAll('path.slice').data(data).enter().append('path').attr('class','slice') |
|
|
.attr('d', arc) |
|
|
.attr('fill', d => colorOf(d.data.category)) |
|
|
.attr('stroke', 'var(--surface-bg)') |
|
|
.attr('stroke-width', 1.2) |
|
|
.attr('data-category', d => d.data.category) |
|
|
.on('mouseenter', function(ev, d){ |
|
|
const hoveredCategory = d.data.category; |
|
|
d3.select(container).classed('hovering', true); |
|
|
d3.select(container).selectAll('path.slice').classed('ghost', s => (s.data && s.data.category) !== hoveredCategory); |
|
|
|
|
|
d3.select(legendHost).selectAll('.item').classed('ghost', function(){ return this.dataset && this.dataset.category !== hoveredCategory; }); |
|
|
d3.select(this).attr('stroke', 'rgba(0,0,0,0.85)').attr('stroke-width', 1); |
|
|
const p = percent(d.data.value); |
|
|
const catColor = colorOf(d.data.category); |
|
|
let html = `<div style="display:flex;align-items:center;gap:8px;white-space:nowrap;"><span class=\"d3-tooltip__color-dot\" style=\"background:${catColor}\"></span><strong>${d.data.category}</strong></div>`; |
|
|
html += `<div>${metric.name}</div>`; |
|
|
html += `<div style="display:flex;align-items:center;gap:6px;white-space:nowrap;"><strong>Value</strong><span style="margin-left:auto;text-align:right;">${d.data.value.toLocaleString()}</span></div>`; |
|
|
tipInner.innerHTML = html; |
|
|
tip.style.opacity = '1'; |
|
|
}) |
|
|
.on('mousemove', function(ev){ |
|
|
const [mx, my] = d3.pointer(ev, container); const offsetX = 12, offsetY = 12; tip.style.transform = `translate(${Math.round(mx+offsetX)}px, ${Math.round(my+offsetY)}px)`; |
|
|
}) |
|
|
.on('mouseleave', function(){ |
|
|
tip.style.opacity='0'; tip.style.transform='translate(-9999px, -9999px)'; |
|
|
d3.select(container).classed('hovering', false); |
|
|
d3.select(container).selectAll('path.slice').classed('ghost', false); |
|
|
d3.select(legendHost).selectAll('.item').classed('ghost', false); |
|
|
d3.select(this).attr('stroke','var(--surface-bg)'); |
|
|
}); |
|
|
|
|
|
|
|
|
gCell.selectAll('text.slice-label').data(data.filter(d => percent(d.data.value) >= 3)).enter() |
|
|
.append('text').attr('class','slice-label').style('pointer-events','none') |
|
|
.attr('transform', d => `translate(${arcLabel.centroid(d)})`) |
|
|
.attr('text-anchor','middle') |
|
|
.text(d => `${percent(d.data.value).toFixed(1)}%`); |
|
|
} |
|
|
|
|
|
|
|
|
const subtitleEl = document.createElement('div'); subtitleEl.className = 'caption-subtitle'; subtitleEl.textContent = metric.title; subtitleEl.style.textAlign = 'center'; cell.appendChild(subtitleEl); |
|
|
const titleEl = document.createElement('div'); titleEl.className = 'caption'; titleEl.textContent = metric.name; titleEl.style.textAlign = 'center'; cell.appendChild(titleEl); |
|
|
}); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
plotsHost.onmouseleave = () => { |
|
|
tip.style.opacity='0'; |
|
|
tip.style.transform='translate(-9999px, -9999px)'; |
|
|
d3.select(container).classed('hovering', false); |
|
|
d3.select(container).selectAll('path.slice').classed('ghost', false); |
|
|
d3.select(legendHost).selectAll('.item').classed('ghost', false); |
|
|
}; |
|
|
} |
|
|
|
|
|
async function init(){ |
|
|
try { |
|
|
const text = await fetchFirstAvailable(CSV_PATHS); |
|
|
const rows = parseCsv(text); |
|
|
drawPies(rows); |
|
|
|
|
|
|
|
|
const rerender = () => drawPies(rows); |
|
|
if (window.ResizeObserver) { const ro = new ResizeObserver(() => rerender()); ro.observe(container); } |
|
|
else { window.addEventListener('resize', rerender); } |
|
|
} catch (err) { |
|
|
const pre = document.createElement('pre'); pre.textContent = (err && err.message) ? err.message : String(err); |
|
|
pre.style.color = 'var(--danger, #b00020)'; pre.style.fontSize = '12px'; pre.style.whiteSpace = 'pre-wrap'; |
|
|
container.appendChild(pre); |
|
|
} |
|
|
} |
|
|
|
|
|
init(); |
|
|
}; |
|
|
|
|
|
if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', () => ensureD3(bootstrap), { once: true }); } else { ensureD3(bootstrap); } |
|
|
})(); |
|
|
</script> |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|