Spaces:
Running
Running
| <!-- Transfer Matrix: 4x4 cross-model AUROC heatmap --> | |
| <div class="transfer-matrix"></div> | |
| <style> | |
| .transfer-matrix { position: relative; width: 100%; min-height: 380px; } | |
| .transfer-matrix svg { display: block; margin: 0 auto; } | |
| .transfer-matrix .cell-text { font-size: 13px; font-weight: 600; text-anchor: middle; font-variant-numeric: tabular-nums; } | |
| .transfer-matrix .axis-text { font-size: 11px; fill: var(--text-color); } | |
| .transfer-matrix .header-text { font-size: 13px; font-weight: 600; fill: var(--text-color); text-anchor: middle; } | |
| .transfer-matrix .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: 260px; | |
| font-variant-numeric: tabular-nums; | |
| } | |
| .transfer-matrix .legend-label { font-size: 10px; fill: var(--text-color); font-variant-numeric: tabular-nums; } | |
| .transfer-matrix .summary { | |
| text-align: center; margin-top: 14px; font-size: 13px; color: var(--text-color); opacity: 0.65; | |
| font-variant-numeric: tabular-nums; | |
| } | |
| @media (max-width: 500px) { | |
| .transfer-matrix { min-height: 300px; } | |
| .transfer-matrix .cell-text { font-size: 11px; } | |
| .transfer-matrix .axis-text { font-size: 9px; } | |
| .transfer-matrix .header-text { font-size: 11px; } | |
| .transfer-matrix .summary { font-size: 11px; } | |
| } | |
| </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('.transfer-matrix:not([data-mounted])'); | |
| if (!container) return; | |
| container.dataset.mounted = 'true'; | |
| const d3 = window.d3; | |
| const models = ['GPT-4.1', 'Claude 3.7', 'GPT-4.1-mini', 'o4-mini']; | |
| const matrix = [ | |
| [0.802, 0.699, 0.671, 0.746], | |
| [0.639, 0.726, 0.730, 0.604], | |
| [0.665, 0.719, 0.790, 0.559], | |
| [0.685, 0.553, 0.604, 0.864], | |
| ]; | |
| const tip = document.createElement('div'); | |
| tip.className = 'tooltip'; | |
| container.appendChild(tip); | |
| const summary = document.createElement('div'); | |
| summary.className = 'summary'; | |
| summary.textContent = 'Diagonal mean: 0.796 | Off-diagonal mean: 0.656'; | |
| container.appendChild(summary); | |
| // Warm earth tones: cream → rust → dark brown | |
| function colorInterp(t) { | |
| const c0 = [245, 240, 232]; // warm cream (#f5f0e8) | |
| const c1 = [61, 90, 128]; // rust (#3d5a80) | |
| const c2 = [92, 38, 24]; // deep rust-brown | |
| let r, g, b; | |
| if (t < 0.5) { | |
| const s = t * 2; | |
| r = c0[0] + (c1[0] - c0[0]) * s; | |
| g = c0[1] + (c1[1] - c0[1]) * s; | |
| b = c0[2] + (c1[2] - c0[2]) * s; | |
| } else { | |
| const s = (t - 0.5) * 2; | |
| r = c1[0] + (c2[0] - c1[0]) * s; | |
| g = c1[1] + (c2[1] - c1[1]) * s; | |
| b = c1[2] + (c2[2] - c1[2]) * s; | |
| } | |
| return `rgb(${Math.round(r)},${Math.round(g)},${Math.round(b)})`; | |
| } | |
| function render() { | |
| container.querySelectorAll('svg').forEach(s => s.remove()); | |
| const rect = container.getBoundingClientRect(); | |
| const maxW = Math.min(520, rect.width - 20); | |
| const cellSize = Math.max(40, Math.floor((maxW - 140) / 4)); | |
| const margin = { top: 56, left: 115, bottom: 60, right: 30 }; | |
| const gridW = cellSize * 4; | |
| const W = margin.left + gridW + margin.right; | |
| const H = margin.top + gridW + margin.bottom; | |
| const svg = d3.select(container).insert('svg', '.tooltip') | |
| .attr('width', W).attr('height', Math.max(0, H)); | |
| const g = svg.append('g').attr('transform', `translate(${margin.left},${margin.top})`); | |
| const colorScale = d3.scaleSequential(t => colorInterp(t)).domain([0.5, 0.9]); | |
| // Headers | |
| svg.append('text').attr('class', 'header-text') | |
| .attr('x', margin.left + gridW / 2).attr('y', 20).text('Test Model'); | |
| svg.append('text').attr('class', 'header-text') | |
| .attr('transform', `translate(18, ${margin.top + gridW / 2}) rotate(-90)`) | |
| .text('Train Model'); | |
| // Column labels | |
| models.forEach((m, i) => { | |
| g.append('text').attr('class', 'axis-text') | |
| .attr('x', i * cellSize + cellSize / 2).attr('y', -12) | |
| .attr('text-anchor', 'middle').text(m); | |
| }); | |
| // Row labels | |
| models.forEach((m, i) => { | |
| g.append('text').attr('class', 'axis-text') | |
| .attr('x', -14).attr('y', i * cellSize + cellSize / 2 + 4) | |
| .attr('text-anchor', 'end').text(m); | |
| }); | |
| // Cells | |
| for (let i = 0; i < 4; i++) { | |
| for (let j = 0; j < 4; j++) { | |
| const val = matrix[i][j]; | |
| const isDiag = i === j; | |
| const cw = Math.max(0, cellSize - 3); | |
| const ch = Math.max(0, cellSize - 3); | |
| const cell = g.append('rect') | |
| .attr('x', j * cellSize + 1.5).attr('y', i * cellSize + 1.5) | |
| .attr('width', cw).attr('height', ch) | |
| .attr('rx', 6).attr('fill', colorScale(val)) | |
| .attr('stroke', isDiag ? 'var(--text-color)' : 'var(--border-color)') | |
| .attr('stroke-width', isDiag ? 2.5 : 0.5) | |
| .attr('stroke-opacity', isDiag ? 0.8 : 0.3) | |
| .attr('cursor', 'pointer') | |
| .style('transition', 'transform 0.12s ease') | |
| .style('transform-origin', `${j * cellSize + cellSize / 2}px ${i * cellSize + cellSize / 2}px`); | |
| cell | |
| .on('mouseenter', function(ev) { | |
| d3.select(this).attr('stroke-width', isDiag ? 3 : 2).attr('stroke-opacity', 1) | |
| .attr('stroke', 'var(--text-color)'); | |
| d3.select(this.parentNode).selectAll(`.cell-text-${i}-${j}`).attr('font-weight', 800); | |
| tip.innerHTML = `<strong>Train:</strong> ${models[i]}<br/><strong>Test:</strong> ${models[j]}<br/><strong>AUROC:</strong> ${val.toFixed(3)}<br/>${isDiag ? 'Self-prediction (diagonal)' : 'Cross-model transfer'}`; | |
| tip.style.opacity = '1'; | |
| }) | |
| .on('mousemove', function(ev) { | |
| const [mx, my] = d3.pointer(ev, container); | |
| const flipX = mx > rect.width * 0.65; | |
| const tx = flipX ? mx - 180 : mx + 14; | |
| tip.style.transform = `translate(${tx}px, ${my - 14}px)`; | |
| }) | |
| .on('mouseleave', function() { | |
| d3.select(this).attr('stroke-width', isDiag ? 2.5 : 0.5) | |
| .attr('stroke-opacity', isDiag ? 0.8 : 0.3) | |
| .attr('stroke', isDiag ? 'var(--text-color)' : 'var(--border-color)'); | |
| d3.select(this.parentNode).selectAll(`.cell-text-${i}-${j}`).attr('font-weight', 600); | |
| tip.style.opacity = '0'; | |
| }); | |
| // Text label: white on dark rust, dark on light cream | |
| const textColor = val > 0.72 ? '#ffffff' : '#4a4540'; | |
| g.append('text').attr('class', `cell-text cell-text-${i}-${j}`) | |
| .attr('x', j * cellSize + cellSize / 2) | |
| .attr('y', i * cellSize + cellSize / 2 + 5) | |
| .attr('fill', textColor) | |
| .style('pointer-events', 'none') | |
| .text(val.toFixed(2)); | |
| } | |
| } | |
| // --- Color legend bar --- | |
| const legendW = Math.max(0, Math.min(200, gridW * 0.7)); | |
| const legendH = 10; | |
| const legendX = (gridW - legendW) / 2; | |
| const legendY = gridW + 18; | |
| const defs = svg.append('defs'); | |
| const gradId = 'tm-grad-' + Math.random().toString(36).slice(2, 8); | |
| const grad = defs.append('linearGradient').attr('id', gradId); | |
| const nStops = 10; | |
| for (let s = 0; s <= nStops; s++) { | |
| const t = s / nStops; | |
| grad.append('stop') | |
| .attr('offset', `${t * 100}%`) | |
| .attr('stop-color', colorInterp(t)); | |
| } | |
| const lg = g.append('g').attr('transform', `translate(${legendX}, ${legendY})`); | |
| lg.append('rect') | |
| .attr('width', Math.max(0, legendW)).attr('height', Math.max(0, legendH)) | |
| .attr('rx', 3).attr('fill', `url(#${gradId})`); | |
| lg.append('text').attr('class', 'legend-label') | |
| .attr('x', 0).attr('y', legendH + 14).attr('text-anchor', 'middle').text('0.50'); | |
| lg.append('text').attr('class', 'legend-label') | |
| .attr('x', legendW / 2).attr('y', legendH + 14).attr('text-anchor', 'middle').text('0.70'); | |
| lg.append('text').attr('class', 'legend-label') | |
| .attr('x', legendW).attr('y', legendH + 14).attr('text-anchor', 'middle').text('0.90'); | |
| lg.append('text').attr('class', 'legend-label') | |
| .attr('x', legendW / 2).attr('y', -6).attr('text-anchor', 'middle') | |
| .attr('font-weight', 500).text('AUROC'); | |
| } | |
| render(); | |
| if (window.ResizeObserver) new ResizeObserver(() => render()).observe(container); | |
| }; | |
| if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', () => ensureD3(bootstrap), { once: true }); | |
| else ensureD3(bootstrap); | |
| })(); | |
| </script> | |