| |
| 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; |
|
|
| |
| let hasAnimated = false; |
|
|
| const observer = new IntersectionObserver((entries) => { |
| entries.forEach(entry => { |
| if (entry.isIntersecting && !hasAnimated) { |
| hasAnimated = true; |
| renderBenchmarkingChart(); |
| observer.disconnect(); |
| } |
| }); |
| }, { |
| threshold: 0.2 |
| }); |
|
|
| observer.observe(container); |
| } |
|
|
| function renderBenchmarkingChart() { |
| const container = document.getElementById('benchmarking-chart'); |
| if (!container) return; |
|
|
| |
| container.style.position = 'relative'; |
|
|
| |
| const allModels = [ |
| ...benchmarkingData.proprietary.map(m => ({ ...m, type: 'proprietary' })), |
| ...benchmarkingData.opensource.map(m => ({ ...m, type: 'opensource' })) |
| ].sort((a, b) => b.acc - a.acc); |
|
|
| |
| 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 |
| }; |
|
|
| |
| const filledTrace = { |
| x: allModels.map(() => 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: '<b>%{y}</b><br>Accuracy: %{x:.2f}%<extra></extra>', |
| 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], |
| 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: ' ' |
| }, |
| 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 |
| }; |
|
|
| |
| Plotly.newPlot(container, [backgroundTrace, filledTrace], layout, config).then(() => { |
| |
| setTimeout(() => { |
| const targetValues = allModels.map(m => m.acc); |
| const duration = 1200; |
| 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 { |
| |
| addLogosWithHTMLOverlay(); |
| } |
| } |
|
|
| animate(); |
| }, 300); |
|
|
| |
| function addLogosWithHTMLOverlay() { |
| |
| const existingLogoContainer = container.querySelector('.logo-overlay-container'); |
| if (existingLogoContainer) { |
| existingLogoContainer.remove(); |
| } |
|
|
| |
| 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); |
|
|
| |
| 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; |
|
|
| |
| allModels.forEach((model, index) => { |
| |
| |
| |
|
|
| const xPixel = xaxis.l2p(model.acc) + xaxis._offset; |
| |
| const yPixel = yaxis.l2p(index) + yaxis._offset; |
|
|
| |
| 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); |
| }); |
| } |
|
|
| |
| window.addEventListener('resize', () => { |
| setTimeout(addLogosWithHTMLOverlay, 100); |
| }); |
| }); |
| } |
|
|