DDR_Bench / benchmarking.js
thinkwee
update animation
7247a32
// 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: '<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], // 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);
});
});
}