AgentStateGraph / app /src /content /embeds /compression-bars.html
seonglae's picture
Automata from Agent Traces — interactive article
59027a2
Raw
History Blame Contribute Delete
8.93 kB
<!-- Compression: log-scale dumbbell of ours vs RPNI state counts across 12 datasets -->
<div class="compression-bars"></div>
<style>
.compression-bars { position: relative; width: 100%; min-height: 440px; }
.compression-bars svg { display: block; width: 100%; }
.compression-bars .tick text {
fill: var(--text-color); font-size: 11px; opacity: 0.6;
font-variant-numeric: tabular-nums;
}
.compression-bars .tick line { display: none; }
.compression-bars .domain { stroke: var(--border-color); opacity: 0.3; }
.compression-bars .y-axis .domain { display: none; }
.compression-bars .axis-label {
fill: var(--text-color); font-size: 12px; font-weight: 500;
}
.compression-bars .cmp-row { transition: opacity 0.15s ease; }
.compression-bars .tooltip {
position: absolute; top: 0; left: 0; pointer-events: none; padding: 10px 14px; border-radius: 8px;
font-size: 12px; line-height: 1.6; border: 1px solid var(--border-color);
background: var(--surface-bg); color: var(--text-color);
box-shadow: 0 4px 20px rgba(0,0,0,0.12), 0 0 0 1px rgba(0,0,0,0.04);
backdrop-filter: blur(12px); -webkit-backdrop-filter: blur(12px);
opacity: 0; transition: opacity 0.15s ease;
z-index: 100; max-width: 240px;
font-variant-numeric: tabular-nums;
}
</style>
<script>
(() => {
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); }
s.addEventListener('load', () => cb(), { once: true });
};
const bootstrap = () => {
const container = document.querySelector('.compression-bars:not([data-mounted])');
if (!container) return;
container.dataset.mounted = 'true';
const d3 = window.d3;
const data = [
{ name: 'WebArena', ours: 25, rpni: 382, compression: 15, fitness: 1.000 },
{ name: 'ATBench', ours: 15, rpni: 899, compression: 60, fitness: 1.000 },
{ name: 'Mind2Web', ours: 8, rpni: 476, compression: 60, fitness: 1.000 },
{ name: 'Who & When', ours: 9, rpni: 971, compression: 108, fitness: 1.000 },
{ name: 'tau2-bench air', ours: 18, rpni: 6506, compression: 361, fitness: 1.000 },
{ name: 'tau2-bench ret', ours: 19, rpni: 14249, compression: 750, fitness: 1.000 },
{ name: 'SWE-smith', ours: 10, rpni: 11631, compression: 1163, fitness: 1.000 },
{ name: 'OSWorld', ours: 27, rpni: 38232, compression: 1416, fitness: 0.997 },
{ name: 'tau2-bench tel', ours: 43, rpni: 63897, compression: 1486, fitness: 1.000 },
{ name: 'SWE-agent', ours: 25, rpni: 59510, compression: 2380, fitness: 0.999 },
{ name: 'AgentNet', ours: 25, rpni: 62495, compression: 2500, fitness: 1.000 },
{ name: 'GUI-Odyssey', ours: 7, rpni: 21255, compression: 3036, fitness: 1.000 },
].sort((a, b) => a.compression - b.compression);
const C_OURS = '#3d5a80'; // strong slate -- ours
const C_RPNI = '#8fa6c4'; // light slate -- RPNI
const margin = { top: 36, right: 78, bottom: 44, left: 120 };
const tip = document.createElement('div');
tip.className = 'tooltip';
container.appendChild(tip);
let firstRender = true;
function render() {
container.querySelectorAll('svg').forEach(s => s.remove());
const rect = container.getBoundingClientRect();
const W = Math.max(300, Math.round(rect.width));
const rowH = 30;
const H = margin.top + margin.bottom + data.length * rowH;
const w = Math.max(0, W - margin.left - margin.right);
const h = Math.max(0, H - margin.top - margin.bottom);
const svg = d3.select(container).insert('svg', '.tooltip')
.attr('width', W).attr('height', H);
const g = svg.append('g').attr('transform', `translate(${margin.left},${margin.top})`);
const x = d3.scaleLog().domain([5, 90000]).range([0, Math.max(0, w)]).clamp(true);
const y = d3.scaleBand().domain(data.map(d => d.name)).range([0, Math.max(0, h)]).padding(0.32);
// decade grid lines
const ticks = [10, 100, 1000, 10000];
g.append('g').selectAll('line').data(ticks).enter().append('line')
.attr('x1', d => x(d)).attr('x2', d => x(d))
.attr('y1', -6).attr('y2', Math.max(0, h))
.attr('stroke', 'var(--border-color)').attr('stroke-opacity', 0.25)
.attr('stroke-dasharray', '2,4');
// x axis
g.append('g').attr('transform', `translate(0,${Math.max(0, h)})`)
.call(d3.axisBottom(x).tickValues(ticks).tickFormat(d => d >= 1000 ? (d / 1000) + 'k' : d + ''))
.call(s => s.select('.domain').attr('opacity', 0.3))
.selectAll('.tick text').attr('opacity', 0.6);
g.append('text').attr('class', 'axis-label')
.attr('x', Math.max(0, w) / 2).attr('y', Math.max(0, h) + 36).attr('text-anchor', 'middle')
.text('State count, log scale (gap = compression)');
// y labels
g.append('g').attr('class', 'y-axis')
.call(d3.axisLeft(y).tickSize(0).tickPadding(10))
.call(s => s.select('.domain').remove())
.selectAll('.tick text').attr('font-size', '11px').attr('opacity', 0.7);
// legend
const leg = g.append('g').attr('transform', 'translate(0, -22)');
leg.append('circle').attr('cx', 0).attr('cy', 0).attr('r', 5).attr('fill', C_OURS);
leg.append('text').attr('x', 10).attr('y', 4).attr('fill', 'var(--text-color)').attr('font-size', '11px').attr('opacity', 0.75).text('Ours (FSM)');
leg.append('circle').attr('cx', 96).attr('cy', 0).attr('r', 5).attr('fill', C_RPNI);
leg.append('text').attr('x', 106).attr('y', 4).attr('fill', 'var(--text-color)').attr('font-size', '11px').attr('opacity', 0.75).text('RPNI');
const rows = g.selectAll('.cmp-row').data(data).enter().append('g')
.attr('class', 'cmp-row')
.attr('transform', d => `translate(0, ${y(d.name) + y.bandwidth() / 2})`)
.style('cursor', 'pointer');
const lines = rows.append('line')
.attr('x1', d => x(d.ours)).attr('y1', 0).attr('y2', 0)
.attr('stroke', C_RPNI).attr('stroke-width', 2).attr('stroke-opacity', 0.5)
.attr('stroke-linecap', 'round');
rows.append('circle').attr('class', 'dot-rpni')
.attr('cx', d => x(d.rpni)).attr('cy', 0).attr('r', 5).attr('fill', C_RPNI);
rows.append('circle').attr('class', 'dot-ours')
.attr('cx', d => x(d.ours)).attr('cy', 0).attr('r', 5).attr('fill', C_OURS);
const ratioText = rows.append('text')
.attr('x', d => x(d.rpni) + 9).attr('y', 4)
.attr('fill', 'var(--text-color)').attr('font-size', '10px').attr('font-weight', 600)
.attr('font-variant-numeric', 'tabular-nums').attr('opacity', 0.85)
.text(d => d.compression.toLocaleString() + '×');
if (firstRender) {
lines.attr('x2', d => x(d.ours))
.transition().duration(650).delay((d, i) => i * 45).ease(d3.easeCubicOut)
.attr('x2', d => x(d.rpni));
rows.select('.dot-rpni').attr('cx', d => x(d.ours))
.transition().duration(650).delay((d, i) => i * 45).ease(d3.easeCubicOut)
.attr('cx', d => x(d.rpni));
ratioText.attr('opacity', 0)
.transition().duration(300).delay((d, i) => i * 45 + 550).attr('opacity', 0.85);
} else {
lines.attr('x2', d => x(d.rpni));
}
rows
.on('mouseenter', function(ev, d) {
d3.select(this).selectAll('circle').attr('r', 6.5);
tip.innerHTML = [
'<strong>' + d.name + '</strong>',
'Ours: ' + d.ours + ' states',
'RPNI: ' + d.rpni.toLocaleString() + ' states',
'Compression: <strong>' + d.compression.toLocaleString() + '×</strong>',
'Fitness: ' + d.fitness.toFixed(3)
].join('<br/>');
tip.style.opacity = '1';
})
.on('mousemove', function(ev) {
const [mx, my] = d3.pointer(ev, container);
const tipW = tip.offsetWidth || 180, tipH = tip.offsetHeight || 80;
const cRect = container.getBoundingClientRect();
let tx = mx + 14, ty = my - 14;
if (tx + tipW > cRect.width) tx = mx - tipW - 14;
if (ty + tipH > cRect.height) ty = my - tipH - 14;
tip.style.transform = `translate(${tx}px, ${ty}px)`;
})
.on('mouseleave', function() {
d3.select(this).selectAll('circle').attr('r', 5);
tip.style.opacity = '0';
});
firstRender = false;
}
render();
if (window.ResizeObserver) new ResizeObserver(() => render()).observe(container);
};
if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', () => ensureD3(bootstrap), { once: true });
else ensureD3(bootstrap);
})();
</script>