|
|
|
|
|
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); |
|
|
}); |
|
|
}); |
|
|
} |
|
|
|