// Benchmarking chart rendering document.addEventListener('DOMContentLoaded', function () { if (typeof benchmarkingData === 'undefined') { console.error('Benchmarking data not loaded'); return; } initBenchmarkingChart(); }); function initBenchmarkingChart() { const container = document.getElementById('benchmarking-chart'); if (!container) return; // Use Intersection Observer to trigger animation when chart comes into view let hasAnimated = false; const observer = new IntersectionObserver((entries) => { entries.forEach(entry => { if (entry.isIntersecting && !hasAnimated) { hasAnimated = true; renderBenchmarkingChart(); observer.disconnect(); // Stop observing after animation starts } }); }, { threshold: 0.2 // Trigger when 20% of the chart is visible }); observer.observe(container); } function renderBenchmarkingChart() { const container = document.getElementById('benchmarking-chart'); if (!container) return; // Ensure container has relative positioning for logo overlay container.style.position = 'relative'; // Combine all models and sort by accuracy (descending) const allModels = [ ...benchmarkingData.proprietary.map(m => ({ ...m, type: 'proprietary' })), ...benchmarkingData.opensource.map(m => ({ ...m, type: 'opensource' })) ].sort((a, b) => b.acc - a.acc); // Create background trace (100% bars) - always visible const backgroundTrace = { x: allModels.map(() => 100), y: allModels.map(m => m.model), type: 'bar', orientation: 'h', name: 'Maximum', marker: { color: 'rgba(0, 0, 0, 0.05)', line: { width: 1, color: 'rgba(0, 0, 0, 0.1)' } }, hoverinfo: 'skip', showlegend: false }; // Create filled trace - starts at 0, will animate to actual values const filledTrace = { x: allModels.map(() => 0), // Start with 0 y: allModels.map(m => m.model), type: 'bar', orientation: 'h', name: 'Accuracy', marker: { color: allModels.map(m => m.type === 'proprietary' ? '#7c3aed' : '#059669' ), line: { width: 0 } }, hovertemplate: '%{y}
Accuracy: %{x:.2f}%', text: allModels.map(m => m.acc.toFixed(2) + '%'), textposition: 'inside', textfont: { color: 'white', size: 11, family: 'var(--font-system)' }, insidetextanchor: 'end' }; const layout = { barmode: 'overlay', xaxis: { title: 'Overall Average Accuracy (%)', range: [0, 100], // Full range showgrid: true, gridcolor: 'rgba(0, 0, 0, 0.05)', zeroline: false, tickfont: { size: 11, color: '#6e6e73', family: 'var(--font-system)' }, titlefont: { size: 13, color: '#424245', family: 'var(--font-system)' } }, yaxis: { autorange: 'reversed', showgrid: false, zeroline: false, tickfont: { size: 13, color: '#1d1d1f', family: 'var(--font-system)' }, tickmode: 'linear', ticksuffix: ' ' // Add padding between model name and bar }, margin: { l: 200, r: 40, t: 20, b: 60 }, paper_bgcolor: 'rgba(0,0,0,0)', plot_bgcolor: 'rgba(0,0,0,0)', showlegend: false, height: 800, hovermode: 'closest', font: { family: 'var(--font-system)' } }; const config = { responsive: true, displayModeBar: false }; // Plot initial state (empty colored bars) Plotly.newPlot(container, [backgroundTrace, filledTrace], layout, config).then(() => { // Animate to filled state with manual frame-by-frame animation setTimeout(() => { const targetValues = allModels.map(m => m.acc); const duration = 1200; // ms const startTime = Date.now(); function easeInOutCubic(t) { return t < 0.5 ? 4 * t * t * t : 1 - Math.pow(-2 * t + 2, 3) / 2; } function animate() { const elapsed = Date.now() - startTime; const progress = Math.min(elapsed / duration, 1); const easedProgress = easeInOutCubic(progress); const currentValues = targetValues.map(val => val * easedProgress); Plotly.restyle(container, { x: [currentValues] }, [1]); if (progress < 1) { requestAnimationFrame(animate); } else { // Animation complete, add logos using HTML overlay addLogosWithHTMLOverlay(); } } animate(); }, 300); // Use Plotly's coordinate conversion to position HTML logos precisely function addLogosWithHTMLOverlay() { // Remove existing logo container if any const existingLogoContainer = container.querySelector('.logo-overlay-container'); if (existingLogoContainer) { existingLogoContainer.remove(); } // Create logo overlay container const logoContainer = document.createElement('div'); logoContainer.className = 'logo-overlay-container'; logoContainer.style.cssText = ` position: absolute; top: 0; left: 0; width: 100%; height: 100%; pointer-events: none; z-index: 10; `; container.appendChild(logoContainer); // Get plot area dimensions from Plotly's internal layout const gd = container; const fullLayout = gd._fullLayout; if (!fullLayout || !fullLayout.xaxis || !fullLayout.yaxis) { console.error('Plotly layout not ready'); return; } const xaxis = fullLayout.xaxis; const yaxis = fullLayout.yaxis; // For each model, calculate the position and add logo allModels.forEach((model, index) => { // Convert data coordinates to pixel coordinates // x position: at the end of the bar (model.acc value) // y position: at the center of the category const xPixel = xaxis.l2p(model.acc) + xaxis._offset; // For categorical y-axis, each category is centered at index position const yPixel = yaxis.l2p(index) + yaxis._offset; // Create logo image const logo = document.createElement('img'); logo.src = model.logo; logo.alt = model.model + ' logo'; logo.style.cssText = ` position: absolute; width: 20px; height: 20px; object-fit: contain; transform: translate(4px, -50%); `; logo.style.left = xPixel + 'px'; logo.style.top = yPixel + 'px'; logoContainer.appendChild(logo); }); } // Re-add logos when window resizes window.addEventListener('resize', () => { setTimeout(addLogosWithHTMLOverlay, 100); }); }); }