|
|
|
|
|
|
|
|
|
|
|
|
|
|
const darkLayout = { |
|
|
paper_bgcolor: 'rgba(0,0,0,0)', |
|
|
plot_bgcolor: 'rgba(0,0,0,0)', |
|
|
font: { |
|
|
family: "-apple-system, BlinkMacSystemFont, 'SF Pro Display', 'Helvetica Neue', sans-serif", |
|
|
color: '#1d1d1f', |
|
|
size: 15 |
|
|
}, |
|
|
xaxis: { |
|
|
gridcolor: '#d2d2d7', |
|
|
linecolor: '#d2d2d7', |
|
|
tickfont: { color: '#424245', size: 14 }, |
|
|
title: { font: { color: '#1d1d1f', size: 15, weight: 600 } }, |
|
|
zerolinecolor: '#d2d2d7' |
|
|
}, |
|
|
yaxis: { |
|
|
gridcolor: '#d2d2d7', |
|
|
linecolor: '#d2d2d7', |
|
|
tickfont: { color: '#424245', size: 14 }, |
|
|
title: { font: { color: '#1d1d1f', size: 15, weight: 600 } }, |
|
|
zerolinecolor: '#d2d2d7' |
|
|
}, |
|
|
legend: { |
|
|
bgcolor: 'rgba(0,0,0,0)', |
|
|
bordercolor: 'rgba(0,0,0,0)', |
|
|
borderwidth: 0, |
|
|
font: { color: '#1d1d1f', size: 14 }, |
|
|
orientation: 'h', |
|
|
y: 0.99, |
|
|
x: 0.5, |
|
|
xanchor: 'center', |
|
|
yanchor: 'top' |
|
|
}, |
|
|
hoverlabel: { |
|
|
bgcolor: '#ffffff', |
|
|
bordercolor: '#d2d2d7', |
|
|
font: { color: '#1d1d1f', size: 14 }, |
|
|
namelength: -1 |
|
|
}, |
|
|
hovermode: 'closest', |
|
|
margin: { t: 20, r: 10, b: 40, l: 50 }, |
|
|
}; |
|
|
|
|
|
const plotlyConfig = { |
|
|
displayModeBar: false, |
|
|
responsive: true, |
|
|
displaylogo: false |
|
|
}; |
|
|
|
|
|
|
|
|
const animationSettings = { |
|
|
transition: { |
|
|
duration: 750, |
|
|
easing: 'cubic-in-out' |
|
|
}, |
|
|
frame: { |
|
|
duration: 750, |
|
|
redraw: true |
|
|
} |
|
|
}; |
|
|
|
|
|
|
|
|
let currentScalingDim = 'turn'; |
|
|
let currentProbingMode = 'byProgress'; |
|
|
let currentRankingMode = 'novelty'; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const initializedCharts = new Set(); |
|
|
|
|
|
|
|
|
const lazyLoadObserver = new IntersectionObserver((entries) => { |
|
|
entries.forEach(entry => { |
|
|
if (entry.isIntersecting) { |
|
|
const section = entry.target; |
|
|
const sectionId = section.id; |
|
|
|
|
|
if (!initializedCharts.has(sectionId)) { |
|
|
initializedCharts.add(sectionId); |
|
|
|
|
|
|
|
|
const initFn = () => { |
|
|
switch (sectionId) { |
|
|
case 'scaling': initScalingCharts(); break; |
|
|
case 'ranking': initRankingCharts(); break; |
|
|
case 'turn': initTurnCharts(); break; |
|
|
case 'entropy': initEntropyCharts(); break; |
|
|
case 'error': initErrorChart(); break; |
|
|
case 'probing': initProbingCharts(); break; |
|
|
} |
|
|
}; |
|
|
|
|
|
if ('requestIdleCallback' in window) { |
|
|
requestIdleCallback(initFn, { timeout: 100 }); |
|
|
} else { |
|
|
setTimeout(initFn, 0); |
|
|
} |
|
|
} |
|
|
} |
|
|
}); |
|
|
}, { |
|
|
rootMargin: '0px 0px', |
|
|
threshold: 0.15 |
|
|
}); |
|
|
|
|
|
|
|
|
function debounce(fn, delay) { |
|
|
let timeoutId; |
|
|
return function (...args) { |
|
|
clearTimeout(timeoutId); |
|
|
timeoutId = setTimeout(() => fn.apply(this, args), delay); |
|
|
}; |
|
|
} |
|
|
|
|
|
|
|
|
function throttle(fn, limit) { |
|
|
let inThrottle = false; |
|
|
return function (...args) { |
|
|
if (!inThrottle) { |
|
|
fn.apply(this, args); |
|
|
inThrottle = true; |
|
|
setTimeout(() => inThrottle = false, limit); |
|
|
} |
|
|
}; |
|
|
} |
|
|
|
|
|
|
|
|
function batchUpdate(updateFn) { |
|
|
return new Promise(resolve => { |
|
|
requestAnimationFrame(() => { |
|
|
updateFn(); |
|
|
resolve(); |
|
|
}); |
|
|
}); |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function normalizeData(values, type) { |
|
|
if (values.length === 0) return { normalized: [], min: 0, max: 1 }; |
|
|
|
|
|
let min, max; |
|
|
let normalized; |
|
|
|
|
|
if (type === 'log') { |
|
|
|
|
|
const positiveValues = values.filter(v => v > 0); |
|
|
min = Math.min(...positiveValues); |
|
|
max = Math.max(...positiveValues); |
|
|
const logMin = Math.log10(min); |
|
|
const logMax = Math.log10(max); |
|
|
const range = logMax - logMin || 1; |
|
|
|
|
|
normalized = values.map(v => v > 0 ? (Math.log10(v) - logMin) / range : 0); |
|
|
} else { |
|
|
min = 0; |
|
|
max = Math.max(...values); |
|
|
const range = max - min || 1; |
|
|
|
|
|
normalized = values.map(v => (v - min) / range); |
|
|
} |
|
|
|
|
|
return { normalized, min, max }; |
|
|
} |
|
|
|
|
|
|
|
|
function generateTicks(min, max, type) { |
|
|
const tickVals = [0, 0.2, 0.4, 0.6, 0.8, 1.0]; |
|
|
let tickText; |
|
|
|
|
|
if (type === 'log') { |
|
|
const logMin = Math.log10(min); |
|
|
const logMax = Math.log10(max); |
|
|
const range = logMax - logMin; |
|
|
|
|
|
tickText = tickVals.map(v => { |
|
|
const val = Math.pow(10, logMin + (v * range)); |
|
|
if (val >= 1) return val.toFixed(1); |
|
|
return val.toFixed(3); |
|
|
}); |
|
|
|
|
|
tickText = tickText.map(t => '$' + t); |
|
|
} else { |
|
|
const range = max - min; |
|
|
tickText = tickVals.map(v => { |
|
|
const val = min + (v * range); |
|
|
if (val >= 1000) return (val / 1000).toFixed(0) + 'k'; |
|
|
return val.toFixed(0); |
|
|
}); |
|
|
} |
|
|
|
|
|
return { tickVals, tickText }; |
|
|
} |
|
|
|
|
|
|
|
|
const SCALING_Y_RANGES = { |
|
|
'mimic': [5, 40], |
|
|
'10k': [0, 85], |
|
|
'globem': [0, 50] |
|
|
}; |
|
|
|
|
|
|
|
|
function populateSharedLegend(containerId, models, colorMap) { |
|
|
const container = document.getElementById(containerId); |
|
|
if (!container) return; |
|
|
|
|
|
container.innerHTML = models.map(model => { |
|
|
const color = (colorMap && colorMap[model]) || '#888'; |
|
|
return `<div class="legend-item"> |
|
|
<span class="legend-color" style="background: ${color}"></span> |
|
|
<span>${model}</span> |
|
|
</div>`; |
|
|
}).join(''); |
|
|
} |
|
|
|
|
|
function initScalingCharts() { |
|
|
|
|
|
if (typeof DDR_DATA === 'undefined' || !DDR_DATA.scaling) { |
|
|
console.warn('DDR_DATA not loaded yet, retrying...'); |
|
|
setTimeout(initScalingCharts, 100); |
|
|
return; |
|
|
} |
|
|
|
|
|
const scenarios = ['mimic', '10k', 'globem']; |
|
|
|
|
|
scenarios.forEach(scenario => { |
|
|
const data = DDR_DATA.scaling[scenario]; |
|
|
if (!data) return; |
|
|
|
|
|
const models = Object.keys(data); |
|
|
const traces = []; |
|
|
|
|
|
|
|
|
const allTurns = models.flatMap(m => data[m].turns); |
|
|
const { normalized: normTurns, min: minTurn, max: maxTurn } = normalizeData(allTurns, 'linear'); |
|
|
const { tickVals, tickText } = generateTicks(minTurn, maxTurn, 'linear'); |
|
|
|
|
|
|
|
|
let offset = 0; |
|
|
models.forEach(model => { |
|
|
const len = data[model].turns.length; |
|
|
const modelNormX = normTurns.slice(offset, offset + len); |
|
|
offset += len; |
|
|
|
|
|
|
|
|
traces.push({ |
|
|
x: modelNormX, |
|
|
y: data[model].accuracy, |
|
|
mode: 'markers', |
|
|
name: model, |
|
|
line: { color: DDR_DATA.modelColors[model] || '#888', width: 2 }, |
|
|
marker: { size: 6, color: DDR_DATA.modelColors[model] || '#888' }, |
|
|
hovertemplate: `<b>${model}</b><br>Turn: %{customdata}<br>Accuracy: %{y:.2f}%<extra></extra>`, |
|
|
customdata: data[model].turns |
|
|
}); |
|
|
}); |
|
|
|
|
|
const yRange = SCALING_Y_RANGES[scenario] || [0, 100]; |
|
|
|
|
|
|
|
|
const dtickVal = scenario === '10k' ? 10 : 5; |
|
|
|
|
|
const layout = { |
|
|
...darkLayout, |
|
|
xaxis: { |
|
|
...darkLayout.xaxis, |
|
|
title: { text: 'Number of Interaction Turns', font: { size: 15, color: '#1d1d1f' } }, |
|
|
type: 'linear', |
|
|
range: [-0.05, 1.05], |
|
|
tickmode: 'array', |
|
|
tickvals: tickVals, |
|
|
ticktext: tickText, |
|
|
zeroline: false |
|
|
}, |
|
|
yaxis: { |
|
|
...darkLayout.yaxis, |
|
|
title: { text: 'Accuracy (%)', font: { size: 15, color: '#1d1d1f' } }, |
|
|
dtick: dtickVal, |
|
|
range: yRange |
|
|
}, |
|
|
showlegend: false |
|
|
}; |
|
|
|
|
|
|
|
|
Plotly.newPlot(`scaling-${scenario}`, traces, layout, plotlyConfig).then(() => { |
|
|
|
|
|
setTimeout(() => { |
|
|
animateScalingLinesIn(`scaling-${scenario}`, models, data, normTurns); |
|
|
}, 300); |
|
|
}); |
|
|
}); |
|
|
|
|
|
|
|
|
const firstScenario = scenarios.find(s => DDR_DATA.scaling[s]); |
|
|
if (firstScenario) { |
|
|
const models = Object.keys(DDR_DATA.scaling[firstScenario]); |
|
|
populateSharedLegend('scaling-legend', models, DDR_DATA.modelColors); |
|
|
} |
|
|
|
|
|
|
|
|
setTimeout(() => applyHoverEffectsForSection('scaling'), 500); |
|
|
} |
|
|
|
|
|
|
|
|
function animateScalingLinesIn(containerId, models, data, normTurns) { |
|
|
const graphDiv = document.getElementById(containerId); |
|
|
if (!graphDiv) return; |
|
|
|
|
|
|
|
|
let offset = 0; |
|
|
const tracesWithLines = models.map(model => { |
|
|
const len = data[model].turns.length; |
|
|
const modelNormX = normTurns.slice(offset, offset + len); |
|
|
offset += len; |
|
|
|
|
|
return { |
|
|
x: modelNormX, |
|
|
y: data[model].accuracy, |
|
|
mode: 'lines+markers', |
|
|
name: model, |
|
|
line: { color: DDR_DATA.modelColors[model] || '#888', width: 2 }, |
|
|
marker: { size: 6, color: DDR_DATA.modelColors[model] || '#888' }, |
|
|
hovertemplate: `<b>${model}</b><br>Turn: %{customdata}<br>Accuracy: %{y:.2f}%<extra></extra>`, |
|
|
customdata: data[model].turns |
|
|
}; |
|
|
}); |
|
|
|
|
|
|
|
|
Plotly.react(containerId, tracesWithLines, graphDiv.layout, plotlyConfig).then(() => { |
|
|
|
|
|
const paths = graphDiv.querySelectorAll('.scatterlayer .trace .lines path'); |
|
|
|
|
|
|
|
|
paths.forEach((path) => { |
|
|
const len = path.getTotalLength(); |
|
|
if (len > 0) { |
|
|
path.style.transition = 'none'; |
|
|
path.style.strokeDasharray = len + ' ' + len; |
|
|
path.style.strokeDashoffset = len; |
|
|
} |
|
|
}); |
|
|
|
|
|
|
|
|
graphDiv.getBoundingClientRect(); |
|
|
|
|
|
|
|
|
requestAnimationFrame(() => { |
|
|
paths.forEach((path, index) => { |
|
|
const len = path.getTotalLength(); |
|
|
if (len > 0) { |
|
|
|
|
|
const delay = index * 80; |
|
|
path.style.transition = `stroke-dashoffset 0.8s ease-out ${delay}ms`; |
|
|
path.style.strokeDashoffset = '0'; |
|
|
} |
|
|
}); |
|
|
}); |
|
|
}); |
|
|
} |
|
|
|
|
|
function updateScalingCharts(dimension) { |
|
|
const scenarios = ['mimic', '10k', 'globem']; |
|
|
const xLabels = { |
|
|
'turn': 'Number of Interaction Turns', |
|
|
'token': 'Total Costed Tokens', |
|
|
'cost': 'Inference Cost ($)' |
|
|
}; |
|
|
|
|
|
scenarios.forEach(scenario => { |
|
|
const data = DDR_DATA.scaling[scenario]; |
|
|
if (!data) return; |
|
|
|
|
|
const models = Object.keys(data); |
|
|
|
|
|
|
|
|
const allRawX = []; |
|
|
models.forEach(model => { |
|
|
switch (dimension) { |
|
|
case 'turn': allRawX.push(...data[model].turns); break; |
|
|
case 'token': allRawX.push(...data[model].tokens); break; |
|
|
case 'cost': allRawX.push(...data[model].costs); break; |
|
|
} |
|
|
}); |
|
|
|
|
|
|
|
|
const type = dimension === 'cost' ? 'log' : 'linear'; |
|
|
const { normalized: allNormX, min: minX, max: maxX } = normalizeData(allRawX, type); |
|
|
const { tickVals, tickText } = generateTicks(minX, maxX, type); |
|
|
|
|
|
|
|
|
const newTraces = []; |
|
|
let offset = 0; |
|
|
|
|
|
const hoverLabels = { 'turn': 'Turns', 'token': 'Tokens', 'cost': 'Cost' }; |
|
|
|
|
|
models.forEach((model, i) => { |
|
|
const len = data[model].turns.length; |
|
|
const modelNormX = allNormX.slice(offset, offset + len); |
|
|
|
|
|
|
|
|
let rawValues; |
|
|
switch (dimension) { |
|
|
case 'turn': rawValues = data[model].turns; break; |
|
|
case 'token': rawValues = data[model].tokens; break; |
|
|
case 'cost': rawValues = data[model].costs; break; |
|
|
} |
|
|
offset += len; |
|
|
|
|
|
newTraces.push({ |
|
|
x: modelNormX, |
|
|
y: data[model].accuracy, |
|
|
customdata: rawValues, |
|
|
name: model, |
|
|
mode: 'lines+markers', |
|
|
hovertemplate: `<b>${model}</b><br>${hoverLabels[dimension]}: %{customdata}<br>Accuracy: %{y:.2f}%<extra></extra>` |
|
|
}); |
|
|
}); |
|
|
|
|
|
|
|
|
const graphDiv = document.getElementById(`scaling-${scenario}`); |
|
|
|
|
|
|
|
|
const markersOnlyTraces = newTraces.map(trace => ({ |
|
|
...trace, |
|
|
mode: 'markers' |
|
|
})); |
|
|
|
|
|
|
|
|
Plotly.relayout(`scaling-${scenario}`, { |
|
|
'xaxis.title.text': xLabels[dimension], |
|
|
'xaxis.tickvals': tickVals, |
|
|
'xaxis.ticktext': tickText |
|
|
}); |
|
|
|
|
|
|
|
|
Plotly.animate(`scaling-${scenario}`, { |
|
|
data: markersOnlyTraces, |
|
|
traces: models.map((_, i) => i) |
|
|
}, { |
|
|
transition: { |
|
|
duration: 500, |
|
|
easing: 'cubic-in-out' |
|
|
}, |
|
|
frame: { |
|
|
duration: 500, |
|
|
redraw: true |
|
|
} |
|
|
}).then(() => { |
|
|
|
|
|
|
|
|
const linesAndMarkersTraces = newTraces.map(trace => ({ |
|
|
...trace, |
|
|
mode: 'lines+markers', |
|
|
line: { |
|
|
...trace.line, |
|
|
|
|
|
width: 0 |
|
|
} |
|
|
})); |
|
|
|
|
|
|
|
|
Plotly.react(`scaling-${scenario}`, linesAndMarkersTraces, { |
|
|
...graphDiv.layout |
|
|
}, plotlyConfig).then(() => { |
|
|
|
|
|
const visibleTraces = newTraces.map(trace => ({ |
|
|
...trace, |
|
|
mode: 'lines+markers' |
|
|
})); |
|
|
|
|
|
|
|
|
const paths = graphDiv.querySelectorAll('.scatterlayer .trace .lines path'); |
|
|
|
|
|
|
|
|
paths.forEach((path) => { |
|
|
const len = path.getTotalLength(); |
|
|
if (len > 0) { |
|
|
path.style.transition = 'none'; |
|
|
path.style.strokeDasharray = len + ' ' + len; |
|
|
path.style.strokeDashoffset = len; |
|
|
} |
|
|
}); |
|
|
|
|
|
|
|
|
Plotly.restyle(`scaling-${scenario}`, { |
|
|
'line.width': models.map(() => 2) |
|
|
}).then(() => { |
|
|
|
|
|
graphDiv.getBoundingClientRect(); |
|
|
|
|
|
|
|
|
requestAnimationFrame(() => { |
|
|
paths.forEach((path) => { |
|
|
const len = path.getTotalLength(); |
|
|
if (len > 0) { |
|
|
path.style.transition = 'stroke-dashoffset 0.8s ease-out'; |
|
|
path.style.strokeDashoffset = '0'; |
|
|
} |
|
|
}); |
|
|
}); |
|
|
}); |
|
|
}); |
|
|
}); |
|
|
}); |
|
|
} |
|
|
|
|
|
|
|
|
document.addEventListener('DOMContentLoaded', () => { |
|
|
const scalingButtons = document.querySelectorAll('#scaling .dim-btn'); |
|
|
scalingButtons.forEach(btn => { |
|
|
btn.addEventListener('click', () => { |
|
|
|
|
|
scalingButtons.forEach(b => b.classList.remove('active')); |
|
|
btn.classList.add('active'); |
|
|
|
|
|
const dimension = btn.dataset.dim; |
|
|
currentScalingDim = dimension; |
|
|
updateScalingCharts(dimension); |
|
|
}); |
|
|
}); |
|
|
}); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const RANKING_DISPLAY_NAMES = { |
|
|
'run_api_deepseek_deepseek-chat': 'DeepSeek-V3.2', |
|
|
'qwen3-next-80b-a3b-instruct': 'Qwen3-Next-80BA3B', |
|
|
'qwen2.5-14B-Instruct-1M': 'Qwen2.5-14B-1M', |
|
|
'qwen2.5-7B-Instruct-1M': 'Qwen2.5-7B-1M', |
|
|
'qwen2.5-14B-Instruct': 'Qwen2.5-14B', |
|
|
'qwen2.5-7B-Instruct': 'Qwen2.5-7B', |
|
|
'qwen2.5-72B-Instruct': 'Qwen2.5-72B', |
|
|
'qwen2.5-32b-instruct': 'Qwen2.5-32B', |
|
|
'qwen3-4B-Instruct-2507': 'Qwen3-4B', |
|
|
'gemini2.5-flash-lite': 'Gemini2.5-Flash-Lite', |
|
|
'gemini2.5-flash': 'Gemini2.5-Flash', |
|
|
'gemini2.5-pro': 'Gemini2.5-Pro', |
|
|
'claude4.5-sonnet': 'Claude4.5-Sonnet', |
|
|
'llama3.3-70B': 'Llama3.3-70B', |
|
|
'minimax-m2': 'MiniMax-M2', |
|
|
'gpt5mini': 'GPT-5-mini', |
|
|
'gpt5-mini': 'GPT-5-mini', |
|
|
'gpt5.1': 'GPT-5.1', |
|
|
'gpt5.2': 'GPT-5.2', |
|
|
'kimi-k2': 'Kimi-K2', |
|
|
'glm4.6': 'GLM-4.6', |
|
|
'qwen3': 'Qwen3-30B-A3B', |
|
|
'gemini3-flash': 'Gemini3-Flash', |
|
|
}; |
|
|
|
|
|
const PROPRIETARY_COLOR = '#6A0DAD'; |
|
|
const OPENSOURCE_COLOR = '#228B22'; |
|
|
|
|
|
function getDisplayName(model) { |
|
|
return RANKING_DISPLAY_NAMES[model] || model; |
|
|
} |
|
|
|
|
|
function renderRankingCharts(mode, animate = false) { |
|
|
const scenarios = [ |
|
|
{ key: 'MIMIC', id: 'mimic' }, |
|
|
{ key: '10K', id: '10k' }, |
|
|
{ key: 'GLOBEM', id: 'globem' } |
|
|
]; |
|
|
|
|
|
scenarios.forEach(({ key, id }) => { |
|
|
const rawData = DDR_DATA.ranking[key]; |
|
|
if (!rawData) return; |
|
|
|
|
|
|
|
|
|
|
|
const baseModels = [...rawData].sort((a, b) => a.bt_rank - b.bt_rank); |
|
|
const topN = baseModels.length; |
|
|
|
|
|
|
|
|
|
|
|
let sortedIndices; |
|
|
if (mode === 'novelty') { |
|
|
|
|
|
sortedIndices = baseModels.map((_, i) => i); |
|
|
} else { |
|
|
|
|
|
|
|
|
const accSorted = [...baseModels].map((m, i) => ({ model: m.model, acc_rank: m.acc_rank, originalIdx: i })) |
|
|
.sort((a, b) => a.acc_rank - b.acc_rank); |
|
|
|
|
|
|
|
|
const indexMap = new Array(topN); |
|
|
accSorted.forEach((item, targetY) => { |
|
|
indexMap[item.originalIdx] = targetY; |
|
|
}); |
|
|
sortedIndices = indexMap; |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
const yValues = sortedIndices.map(idx => topN - 1 - idx); |
|
|
const xBt = baseModels.map(m => m.bt_rank); |
|
|
const xAcc = baseModels.map(m => m.acc_rank); |
|
|
const names = baseModels.map(m => getDisplayName(m.model)); |
|
|
const colors = baseModels.map(m => m.is_proprietary ? PROPRIETARY_COLOR : OPENSOURCE_COLOR); |
|
|
|
|
|
const traces = []; |
|
|
|
|
|
|
|
|
const lineX = []; |
|
|
const lineY = []; |
|
|
baseModels.forEach((_, i) => { |
|
|
lineX.push(xBt[i], xAcc[i], null); |
|
|
lineY.push(yValues[i], yValues[i], null); |
|
|
}); |
|
|
|
|
|
traces.push({ |
|
|
x: lineX, |
|
|
y: lineY, |
|
|
mode: 'lines', |
|
|
line: { |
|
|
color: 'rgba(148, 163, 184, 0.4)', |
|
|
width: 1.5, |
|
|
dash: 'dash' |
|
|
}, |
|
|
showlegend: false, |
|
|
hoverinfo: 'skip' |
|
|
}); |
|
|
|
|
|
|
|
|
traces.push({ |
|
|
x: xBt, |
|
|
y: yValues, |
|
|
mode: 'markers', |
|
|
name: 'Novelty Rank', |
|
|
marker: { |
|
|
size: mode === 'novelty' ? 12 : 10, |
|
|
symbol: 'circle', |
|
|
color: colors, |
|
|
line: { color: '#fff', width: 1.5 } |
|
|
}, |
|
|
text: baseModels.map(m => `<b>${getDisplayName(m.model)}</b><br>Novelty: #${m.bt_rank}<br>Win Rate: ${m.win_rate}%`), |
|
|
hovertemplate: '%{text}<extra></extra>' |
|
|
}); |
|
|
|
|
|
|
|
|
traces.push({ |
|
|
x: xAcc, |
|
|
y: yValues, |
|
|
mode: 'markers', |
|
|
name: 'Accuracy Rank', |
|
|
marker: { |
|
|
size: mode === 'accuracy' ? 12 : 10, |
|
|
symbol: 'diamond-open', |
|
|
color: colors, |
|
|
line: { width: 2 } |
|
|
}, |
|
|
text: baseModels.map(m => `<b>${getDisplayName(m.model)}</b><br>Accuracy: #${m.acc_rank}<br>${m.accuracy}%`), |
|
|
hovertemplate: '%{text}<extra></extra>' |
|
|
}); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const labelX = new Array(topN).fill(topN + 1); |
|
|
|
|
|
traces.push({ |
|
|
x: labelX, |
|
|
y: yValues, |
|
|
mode: 'text', |
|
|
text: names, |
|
|
textposition: 'middle left', |
|
|
textfont: { size: 10, color: '#515154', family: '-apple-system, BlinkMacSystemFont, "SF Pro Text", sans-serif' }, |
|
|
hoverinfo: 'skip', |
|
|
showlegend: false |
|
|
}); |
|
|
|
|
|
|
|
|
const btRanks = baseModels.map(m => m.bt_rank); |
|
|
const accRanks = baseModels.map(m => m.acc_rank); |
|
|
const n = btRanks.length; |
|
|
const meanBt = btRanks.reduce((a, b) => a + b, 0) / n; |
|
|
const meanAcc = accRanks.reduce((a, b) => a + b, 0) / n; |
|
|
let num = 0, denBt = 0, denAcc = 0; |
|
|
for (let i = 0; i < n; i++) { |
|
|
num += (btRanks[i] - meanBt) * (accRanks[i] - meanAcc); |
|
|
denBt += (btRanks[i] - meanBt) ** 2; |
|
|
denAcc += (accRanks[i] - meanAcc) ** 2; |
|
|
} |
|
|
const rho = num / Math.sqrt(denBt * denAcc); |
|
|
|
|
|
const sortLabel = mode === 'novelty' ? 'Sorted by Novelty' : 'Sorted by Accuracy'; |
|
|
|
|
|
const layout = { |
|
|
...darkLayout, |
|
|
xaxis: { |
|
|
...darkLayout.xaxis, |
|
|
title: { text: 'Rank', font: { size: 10, color: '#1d1d1f' } }, |
|
|
range: [topN + 8, 0.5], |
|
|
tickmode: 'array', |
|
|
tickvals: Array.from({ length: topN }, (_, i) => i + 1), |
|
|
zeroline: false |
|
|
}, |
|
|
yaxis: { |
|
|
...darkLayout.yaxis, |
|
|
showticklabels: false, |
|
|
automargin: false, |
|
|
range: [-1, topN + 2], |
|
|
zeroline: false |
|
|
}, |
|
|
showlegend: false, |
|
|
annotations: [ |
|
|
{ |
|
|
x: 0.02, |
|
|
y: 0.98, |
|
|
xref: 'paper', |
|
|
yref: 'paper', |
|
|
text: `ρ = ${rho.toFixed(2)}`, |
|
|
showarrow: false, |
|
|
font: { size: 11, color: '#515154', family: '-apple-system, BlinkMacSystemFont, "SF Pro Text", sans-serif' }, |
|
|
bgcolor: 'rgba(255, 255, 255, 0.9)', |
|
|
borderpad: 4 |
|
|
}, |
|
|
{ |
|
|
x: 0.98, |
|
|
y: 0.98, |
|
|
xref: 'paper', |
|
|
yref: 'paper', |
|
|
text: sortLabel, |
|
|
showarrow: false, |
|
|
font: { size: 10, color: mode === 'novelty' ? PROPRIETARY_COLOR : OPENSOURCE_COLOR, family: '-apple-system, BlinkMacSystemFont, "SF Pro Text", sans-serif' }, |
|
|
bgcolor: 'rgba(255, 255, 255, 0.9)', |
|
|
borderpad: 4 |
|
|
} |
|
|
], |
|
|
|
|
|
|
|
|
margin: { t: 15, r: 15, b: 40, l: 20 } |
|
|
}; |
|
|
|
|
|
if (animate) { |
|
|
Plotly.animate(`ranking-${id}`, { |
|
|
data: traces, |
|
|
layout: layout |
|
|
}, animationSettings); |
|
|
} else { |
|
|
Plotly.newPlot(`ranking-${id}`, traces, layout, plotlyConfig); |
|
|
} |
|
|
}); |
|
|
} |
|
|
|
|
|
function initRankingCharts() { |
|
|
|
|
|
if (typeof DDR_DATA === 'undefined' || !DDR_DATA.ranking) { |
|
|
setTimeout(initRankingCharts, 100); |
|
|
return; |
|
|
} |
|
|
renderRankingCharts('novelty', false); |
|
|
|
|
|
|
|
|
setTimeout(() => { |
|
|
['mimic', '10k', 'globem'].forEach((id, index) => { |
|
|
const chart = document.getElementById(`ranking-${id}`); |
|
|
if (chart) { |
|
|
chart.style.opacity = '0'; |
|
|
chart.style.transition = `opacity 0.6s ease-out ${index * 150}ms`; |
|
|
requestAnimationFrame(() => { |
|
|
chart.style.opacity = '1'; |
|
|
}); |
|
|
} |
|
|
}); |
|
|
}, 100); |
|
|
} |
|
|
|
|
|
|
|
|
document.addEventListener('DOMContentLoaded', () => { |
|
|
const rankingButtons = document.querySelectorAll('#ranking .dim-btn'); |
|
|
rankingButtons.forEach(btn => { |
|
|
btn.addEventListener('click', () => { |
|
|
const mode = btn.dataset.mode; |
|
|
if (mode === currentRankingMode) return; |
|
|
|
|
|
|
|
|
rankingButtons.forEach(b => b.classList.remove('active')); |
|
|
btn.classList.add('active'); |
|
|
|
|
|
currentRankingMode = mode; |
|
|
renderRankingCharts(mode, true); |
|
|
}); |
|
|
}); |
|
|
}); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const TURN_DISPLAY_NAMES = { |
|
|
'run_api_deepseek_deepseek-chat': 'DeepSeek-V3.2', |
|
|
'qwen3-next-80b-a3b-instruct': 'Qwen3-Next-80A3B', |
|
|
'qwen3-next-80b-a3b-instruct-note': 'Qwen3-Next-80A3B-Note', |
|
|
'qwen3-next-80b-a3b-instruct-noreasoning': 'Qwen3-Next-80A3B-NoR', |
|
|
'qwen3-next-80b-a3b-instruct-longreasoning': 'Qwen3-Next-80A3B-LR', |
|
|
'qwen3-next-80b-a3b-instruct-shortreasoning': 'Qwen3-Next-80A3B-SR', |
|
|
'qwen2.5-14B-Instruct-1M': 'Qwen2.5-14B-1M', |
|
|
'qwen2.5-7B-Instruct-1M': 'Qwen2.5-7B-1M', |
|
|
'qwen2.5-14B-Instruct': 'Qwen2.5-14B', |
|
|
'qwen2.5-7B-Instruct': 'Qwen2.5-7B', |
|
|
'qwen2.5-72B-Instruct': 'Qwen2.5-72B', |
|
|
'qwen2.5-32b-instruct': 'Qwen2.5-32B', |
|
|
'qwen3-4B-Instruct-2507': 'Qwen3-4B', |
|
|
'gemini2.5-flash-lite': 'Gemini2.5-Flash-Lite', |
|
|
'gemini2.5-flash': 'Gemini2.5-Flash', |
|
|
'gemini2.5-pro': 'Gemini2.5-Pro', |
|
|
'claude4.5-sonnet': 'Claude4.5-Sonnet', |
|
|
'llama3.3-70B': 'Llama3.3-70B', |
|
|
'llama-3.3-70B': 'Llama3.3-70B', |
|
|
'minimax-m2': 'MiniMax-M2', |
|
|
'gpt5mini': 'GPT-5-mini', |
|
|
'gpt5-mini': 'GPT-5-mini', |
|
|
'gpt5.1': 'GPT-5.1', |
|
|
'gpt5.2': 'GPT-5.2', |
|
|
'kimi-k2': 'Kimi-K2', |
|
|
'glm4.6': 'GLM-4.6', |
|
|
'qwen3': 'Qwen3-30B-A3B', |
|
|
'gemini3-flash': 'Gemini3-Flash', |
|
|
}; |
|
|
|
|
|
function getTurnDisplayName(model) { |
|
|
return TURN_DISPLAY_NAMES[model] || model; |
|
|
} |
|
|
|
|
|
function initTurnCharts() { |
|
|
|
|
|
if (typeof DDR_DATA === 'undefined' || !DDR_DATA.turn) { |
|
|
setTimeout(initTurnCharts, 100); |
|
|
return; |
|
|
} |
|
|
|
|
|
const scenarios = ['mimic', '10k', 'globem']; |
|
|
|
|
|
|
|
|
const familyColors = { |
|
|
'claude': '#D97706', |
|
|
'gpt': '#10A37F', |
|
|
'gemini': '#4285F4', |
|
|
'deepseek': '#1E3A8A', |
|
|
'glm': '#7C3AED', |
|
|
'kimi': '#DC2626', |
|
|
'minimax': '#EC4899', |
|
|
'qwen': '#0EA5E9', |
|
|
'llama': '#F59E0B' |
|
|
}; |
|
|
|
|
|
function getModelColor(modelName) { |
|
|
const lower = modelName.toLowerCase(); |
|
|
for (const [family, color] of Object.entries(familyColors)) { |
|
|
if (lower.includes(family)) return color; |
|
|
} |
|
|
return '#666666'; |
|
|
} |
|
|
|
|
|
scenarios.forEach(scenario => { |
|
|
const data = DDR_DATA.turn[scenario]; |
|
|
if (!data) return; |
|
|
|
|
|
|
|
|
const sortedData = [...data].sort((a, b) => b.median - a.median); |
|
|
|
|
|
|
|
|
const displayData = sortedData.slice(0, 15).reverse(); |
|
|
|
|
|
const traces = []; |
|
|
const binCenters = [5, 15, 25, 35, 45, 55, 65, 75, 85, 95]; |
|
|
|
|
|
displayData.forEach((model, idx) => { |
|
|
const color = getModelColor(model.model); |
|
|
const yOffset = idx; |
|
|
const displayName = getTurnDisplayName(model.model); |
|
|
const maxDist = Math.max(...model.distribution) || 1; |
|
|
|
|
|
|
|
|
const binCenters = [5, 15, 25, 35, 45, 55, 65, 75, 85, 95]; |
|
|
const binValues = model.distribution.map(d => d / maxDist * 0.75); |
|
|
|
|
|
|
|
|
const xSmooth = []; |
|
|
const ySmooth = []; |
|
|
|
|
|
|
|
|
xSmooth.push(0); |
|
|
ySmooth.push(yOffset); |
|
|
|
|
|
|
|
|
for (let i = 0; i < binCenters.length; i++) { |
|
|
xSmooth.push(binCenters[i]); |
|
|
ySmooth.push(yOffset + binValues[i]); |
|
|
} |
|
|
|
|
|
|
|
|
xSmooth.push(100); |
|
|
ySmooth.push(yOffset); |
|
|
|
|
|
|
|
|
traces.push({ |
|
|
x: xSmooth, |
|
|
y: ySmooth, |
|
|
mode: 'lines', |
|
|
line: { |
|
|
color: color, |
|
|
width: 2, |
|
|
shape: 'spline', |
|
|
smoothing: 1.3 |
|
|
}, |
|
|
fill: 'toself', |
|
|
fillcolor: color + '60', |
|
|
name: displayName, |
|
|
hovertemplate: `<b>${displayName}</b><br>Median: ${model.median}<extra></extra>`, |
|
|
showlegend: false |
|
|
}); |
|
|
}); |
|
|
|
|
|
const layout = { |
|
|
...darkLayout, |
|
|
xaxis: { |
|
|
...darkLayout.xaxis, |
|
|
title: { text: 'Number of Turns', font: { size: 14, color: '#1d1d1f' } }, |
|
|
range: scenario === 'globem' ? [0, 40] : [0, 80], |
|
|
dtick: 20 |
|
|
}, |
|
|
yaxis: { |
|
|
...darkLayout.yaxis, |
|
|
tickmode: 'array', |
|
|
tickvals: displayData.map((_, i) => i + 0.35), |
|
|
ticktext: displayData.map(m => getTurnDisplayName(m.model)), |
|
|
tickfont: { size: 10, color: '#424245' }, |
|
|
automargin: true, |
|
|
range: [-0.5, displayData.length], |
|
|
showgrid: false, |
|
|
zeroline: false |
|
|
}, |
|
|
margin: { ...darkLayout.margin, l: 85 }, |
|
|
showlegend: false |
|
|
}; |
|
|
|
|
|
Plotly.newPlot(`turn-${scenario}`, traces, layout, plotlyConfig).then(() => { |
|
|
|
|
|
const graphDiv = document.getElementById(`turn-${scenario}`); |
|
|
if (!graphDiv) return; |
|
|
|
|
|
|
|
|
const paths = graphDiv.querySelectorAll('.scatterlayer .trace path'); |
|
|
paths.forEach((path, index) => { |
|
|
const len = path.getTotalLength(); |
|
|
if (len > 0) { |
|
|
path.style.transition = 'none'; |
|
|
path.style.strokeDasharray = len + ' ' + len; |
|
|
path.style.strokeDashoffset = len; |
|
|
path.style.opacity = '0'; |
|
|
|
|
|
|
|
|
const delay = index * 50; |
|
|
requestAnimationFrame(() => { |
|
|
path.style.transition = `stroke-dashoffset 0.8s ease-out ${delay}ms, opacity 0.4s ease-out ${delay}ms`; |
|
|
path.style.strokeDashoffset = '0'; |
|
|
path.style.opacity = '1'; |
|
|
}); |
|
|
} |
|
|
}); |
|
|
}); |
|
|
}); |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
let probingChartsInitialized = false; |
|
|
|
|
|
function initProbingCharts() { |
|
|
|
|
|
if (typeof DDR_DATA === 'undefined' || !DDR_DATA.probing) { |
|
|
setTimeout(initProbingCharts, 100); |
|
|
return; |
|
|
} |
|
|
renderProbingCharts('byProgress'); |
|
|
|
|
|
|
|
|
if (!probingChartsInitialized) { |
|
|
probingChartsInitialized = true; |
|
|
setTimeout(() => { |
|
|
['mimic', 'globem', '10k'].forEach((scenario, scenarioIndex) => { |
|
|
const graphDiv = document.getElementById(`probing-${scenario}`); |
|
|
if (!graphDiv) return; |
|
|
|
|
|
const paths = graphDiv.querySelectorAll('.scatterlayer .trace .lines path'); |
|
|
paths.forEach((path, index) => { |
|
|
const len = path.getTotalLength(); |
|
|
if (len > 0) { |
|
|
path.style.transition = 'none'; |
|
|
path.style.strokeDasharray = len + ' ' + len; |
|
|
path.style.strokeDashoffset = len; |
|
|
|
|
|
const delay = scenarioIndex * 100 + index * 60; |
|
|
requestAnimationFrame(() => { |
|
|
path.style.transition = `stroke-dashoffset 0.8s ease-out ${delay}ms`; |
|
|
path.style.strokeDashoffset = '0'; |
|
|
}); |
|
|
} |
|
|
}); |
|
|
}); |
|
|
}, 200); |
|
|
} |
|
|
} |
|
|
|
|
|
function renderProbingCharts(mode) { |
|
|
const scenarios = ['mimic', 'globem', '10k']; |
|
|
const scenarioIds = { 'mimic': 'mimic', 'globem': 'globem', '10k': '10k' }; |
|
|
|
|
|
scenarios.forEach(scenario => { |
|
|
const modeKey = mode === 'byTurn' ? 'byTurn' : 'byProgress'; |
|
|
const data = DDR_DATA.probing[modeKey]?.[scenario]; |
|
|
if (!data) return; |
|
|
|
|
|
const traces = []; |
|
|
const allModels = Object.keys(data); |
|
|
|
|
|
const models = allModels.filter(m => !m.includes('7B') && !m.includes('14B')); |
|
|
|
|
|
models.forEach(model => { |
|
|
const modelData = data[model]; |
|
|
const xKey = mode === 'byTurn' ? 'turns' : 'progress'; |
|
|
const xLabel = mode === 'byTurn' ? 'Turn' : 'Progress (%)'; |
|
|
|
|
|
|
|
|
traces.push({ |
|
|
x: modelData[xKey], |
|
|
y: modelData.logprob, |
|
|
mode: 'lines+markers', |
|
|
name: model, |
|
|
line: { |
|
|
color: (DDR_DATA.modelColors && DDR_DATA.modelColors[model]) || '#888', |
|
|
width: 2 |
|
|
}, |
|
|
marker: { size: 6, color: (DDR_DATA.modelColors && DDR_DATA.modelColors[model]) || '#888' }, |
|
|
hovertemplate: `<b>${model}</b><br>${xLabel}: %{x}<br>Log Prob: %{y:.2f}<extra></extra>` |
|
|
}); |
|
|
|
|
|
|
|
|
if (modelData.sem) { |
|
|
const upper = modelData.logprob.map((v, i) => v + modelData.sem[i]); |
|
|
const lower = modelData.logprob.map((v, i) => v - modelData.sem[i]); |
|
|
|
|
|
traces.push({ |
|
|
x: [...modelData[xKey], ...modelData[xKey].slice().reverse()], |
|
|
y: [...upper, ...lower.slice().reverse()], |
|
|
fill: 'toself', |
|
|
fillcolor: ((DDR_DATA.modelColors && DDR_DATA.modelColors[model]) || '#888') + '25', |
|
|
line: { width: 0 }, |
|
|
showlegend: false, |
|
|
hoverinfo: 'skip' |
|
|
}); |
|
|
} |
|
|
}); |
|
|
|
|
|
|
|
|
const xaxisConfig = mode === 'byTurn' ? { |
|
|
title: { text: 'Turn', font: { size: 11, color: '#1d1d1f' } }, |
|
|
range: [0.5, 10.5], |
|
|
dtick: 1 |
|
|
} : { |
|
|
title: { text: 'Interaction Progress (%)', font: { size: 11, color: '#1d1d1f' } }, |
|
|
range: [0, 100], |
|
|
dtick: 10 |
|
|
}; |
|
|
|
|
|
const layout = { |
|
|
...darkLayout, |
|
|
xaxis: { |
|
|
...darkLayout.xaxis, |
|
|
...xaxisConfig |
|
|
}, |
|
|
yaxis: { |
|
|
...darkLayout.yaxis, |
|
|
title: { text: 'Avg Log Probability', font: { size: 11, color: '#1d1d1f' } } |
|
|
}, |
|
|
showlegend: false |
|
|
}; |
|
|
|
|
|
const chartId = `probing-${scenarioIds[scenario]}`; |
|
|
|
|
|
|
|
|
const chartDiv = document.getElementById(chartId); |
|
|
if (chartDiv && chartDiv.data) { |
|
|
|
|
|
Plotly.animate(chartId, { |
|
|
data: traces, |
|
|
layout: layout |
|
|
}, animationSettings); |
|
|
} else { |
|
|
|
|
|
Plotly.newPlot(chartId, traces, layout, plotlyConfig); |
|
|
} |
|
|
}); |
|
|
|
|
|
|
|
|
const firstScenario = scenarios.find(s => DDR_DATA.probing[mode === 'byTurn' ? 'byTurn' : 'byProgress']?.[s]); |
|
|
if (firstScenario) { |
|
|
const allModels = Object.keys(DDR_DATA.probing[mode === 'byTurn' ? 'byTurn' : 'byProgress'][firstScenario]); |
|
|
const filteredModels = allModels.filter(m => !m.includes('7B') && !m.includes('14B')); |
|
|
populateSharedLegend('probing-legend', filteredModels, DDR_DATA.modelColors); |
|
|
} |
|
|
|
|
|
|
|
|
setTimeout(() => applyHoverEffectsForSection('probing'), 100); |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function initErrorChart() { |
|
|
|
|
|
if (typeof DDR_DATA === 'undefined') { |
|
|
setTimeout(initErrorChart, 100); |
|
|
return; |
|
|
} |
|
|
|
|
|
const data = DDR_DATA.error; |
|
|
if (!data || data.length === 0) return; |
|
|
|
|
|
|
|
|
const categoryGroups = {}; |
|
|
data.forEach((item, idx) => { |
|
|
if (!categoryGroups[item.main_category]) { |
|
|
categoryGroups[item.main_category] = { start: idx, end: idx, items: [] }; |
|
|
} |
|
|
categoryGroups[item.main_category].end = idx; |
|
|
categoryGroups[item.main_category].items.push(item); |
|
|
}); |
|
|
|
|
|
const traces = [{ |
|
|
x: data.map(d => d.subcategory), |
|
|
y: data.map(d => d.percentage), |
|
|
type: 'bar', |
|
|
marker: { |
|
|
color: data.map(d => d.color), |
|
|
line: { color: '#fff', width: 0.5 } |
|
|
}, |
|
|
text: data.map(d => `${d.percentage}%`), |
|
|
textposition: 'outside', |
|
|
textfont: { size: 14, color: '#1d1d1f' }, |
|
|
hovertemplate: '<b>%{x}</b><br>%{y:.1f}%<br>Count: %{customdata}<extra></extra>', |
|
|
customdata: data.map(d => d.count), |
|
|
showlegend: false |
|
|
}]; |
|
|
|
|
|
const maxPct = Math.max(...data.map(d => d.percentage)); |
|
|
|
|
|
|
|
|
const annotations = []; |
|
|
Object.entries(categoryGroups).forEach(([catName, group]) => { |
|
|
const midIdx = (group.start + group.end) / 2; |
|
|
annotations.push({ |
|
|
x: midIdx, |
|
|
y: maxPct * 1.15, |
|
|
text: `<b>${catName}</b>`, |
|
|
showarrow: false, |
|
|
font: { size: 13, color: '#1d1d1f' }, |
|
|
xanchor: 'center', |
|
|
yanchor: 'bottom' |
|
|
}); |
|
|
}); |
|
|
|
|
|
const layout = { |
|
|
...darkLayout, |
|
|
xaxis: { |
|
|
...darkLayout.xaxis, |
|
|
tickangle: 0, |
|
|
tickfont: { size: 14, color: '#515154' } |
|
|
}, |
|
|
yaxis: { |
|
|
...darkLayout.yaxis, |
|
|
title: { text: 'Percentage (%)', font: { size: 15, color: '#1d1d1f' } }, |
|
|
range: [0, maxPct * 1.25] |
|
|
}, |
|
|
annotations: annotations, |
|
|
margin: { t: 50, r: 20, b: 100, l: 50 } |
|
|
}; |
|
|
|
|
|
|
|
|
const initialTraces = [{ |
|
|
...traces[0], |
|
|
y: data.map(() => 0), |
|
|
text: data.map(() => '') |
|
|
}]; |
|
|
|
|
|
Plotly.newPlot('error-chart', initialTraces, layout, plotlyConfig).then(() => { |
|
|
|
|
|
setTimeout(() => { |
|
|
Plotly.animate('error-chart', { |
|
|
data: traces, |
|
|
traces: [0] |
|
|
}, { |
|
|
transition: { |
|
|
duration: 800, |
|
|
easing: 'cubic-out' |
|
|
}, |
|
|
frame: { |
|
|
duration: 800, |
|
|
redraw: true |
|
|
} |
|
|
}); |
|
|
}, 200); |
|
|
}); |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const ENTROPY_MODELS = [ |
|
|
'GPT-5.2', |
|
|
'Claude-4.5-Sonnet', |
|
|
'Gemini-3-Flash', |
|
|
'GLM-4.6', |
|
|
'Qwen3-Next-80B-A3B', |
|
|
'DeepSeek-V3.2' |
|
|
]; |
|
|
|
|
|
let currentEntropyScenario = '10k'; |
|
|
|
|
|
let entropyChartsInitialized = false; |
|
|
|
|
|
function initEntropyCharts() { |
|
|
if (typeof ENTROPY_DATA === 'undefined') { |
|
|
|
|
|
setTimeout(initEntropyCharts, 100); |
|
|
return; |
|
|
} |
|
|
|
|
|
|
|
|
document.querySelectorAll('[data-entropy-scenario]').forEach(btn => { |
|
|
btn.addEventListener('click', () => { |
|
|
document.querySelectorAll('[data-entropy-scenario]').forEach(b => b.classList.remove('active')); |
|
|
btn.classList.add('active'); |
|
|
currentEntropyScenario = btn.dataset.entropyScenario; |
|
|
renderEntropyCharts(currentEntropyScenario); |
|
|
}); |
|
|
}); |
|
|
|
|
|
|
|
|
renderEntropyCharts('10k'); |
|
|
|
|
|
|
|
|
if (!entropyChartsInitialized) { |
|
|
entropyChartsInitialized = true; |
|
|
setTimeout(() => { |
|
|
for (let i = 0; i < 6; i++) { |
|
|
const chart = document.getElementById(`entropy-model-${i}`); |
|
|
if (chart) { |
|
|
chart.style.opacity = '0'; |
|
|
chart.style.transform = 'scale(0.95)'; |
|
|
chart.style.transition = `opacity 0.5s ease-out ${i * 100}ms, transform 0.5s ease-out ${i * 100}ms`; |
|
|
requestAnimationFrame(() => { |
|
|
chart.style.opacity = '1'; |
|
|
chart.style.transform = 'scale(1)'; |
|
|
}); |
|
|
} |
|
|
} |
|
|
}, 100); |
|
|
} |
|
|
} |
|
|
|
|
|
function renderEntropyCharts(scenario) { |
|
|
const entropyData = ENTROPY_DATA; |
|
|
const datasetInfo = entropyData.datasets[scenario]; |
|
|
|
|
|
if (!datasetInfo) { |
|
|
console.error(`No entropy data for scenario: ${scenario}`); |
|
|
return; |
|
|
} |
|
|
|
|
|
const points = datasetInfo.points; |
|
|
const yMax = datasetInfo.y_max || 1; |
|
|
const accMin = datasetInfo.acc_min || 0; |
|
|
const accMax = datasetInfo.acc_max || 100; |
|
|
const hasAccRange = accMax > accMin; |
|
|
const colors = entropyData.modelColors; |
|
|
|
|
|
|
|
|
const modelGroups = {}; |
|
|
points.forEach(p => { |
|
|
if (!modelGroups[p.model]) { |
|
|
modelGroups[p.model] = []; |
|
|
} |
|
|
modelGroups[p.model].push(p); |
|
|
}); |
|
|
|
|
|
|
|
|
ENTROPY_MODELS.forEach((model, idx) => { |
|
|
const chartId = `entropy-model-${idx}`; |
|
|
const titleId = `entropy-model-${idx}-title`; |
|
|
const color = colors[model] || '#888888'; |
|
|
const pts = modelGroups[model] || []; |
|
|
|
|
|
|
|
|
const titleEl = document.getElementById(titleId); |
|
|
if (titleEl) { |
|
|
titleEl.textContent = `${model} (n=${pts.length})`; |
|
|
} |
|
|
|
|
|
if (pts.length === 0) { |
|
|
|
|
|
const layout = { |
|
|
...darkLayout, |
|
|
xaxis: { ...darkLayout.xaxis, range: [0.6, 1.05], title: { text: 'Entropy', font: { size: 10, color: '#1d1d1f' } } }, |
|
|
yaxis: { ...darkLayout.yaxis, range: [-0.05, yMax], title: { text: 'Coverage', font: { size: 10, color: '#1d1d1f' } } }, |
|
|
annotations: [{ |
|
|
text: 'No data', |
|
|
xref: 'paper', yref: 'paper', |
|
|
x: 0.5, y: 0.5, |
|
|
showarrow: false, |
|
|
font: { size: 14, color: '#888' } |
|
|
}] |
|
|
}; |
|
|
Plotly.newPlot(chartId, [], layout, plotlyConfig); |
|
|
return; |
|
|
} |
|
|
|
|
|
|
|
|
const alphas = pts.map(p => { |
|
|
if (hasAccRange) { |
|
|
return 0.15 + (p.accuracy - accMin) / (accMax - accMin) * 0.85; |
|
|
} |
|
|
return 0.7; |
|
|
}); |
|
|
|
|
|
const trace = { |
|
|
x: pts.map(p => p.entropy), |
|
|
y: pts.map(p => p.coverage), |
|
|
mode: 'markers', |
|
|
type: 'scatter', |
|
|
marker: { |
|
|
color: color, |
|
|
size: 7, |
|
|
opacity: alphas, |
|
|
line: { color: '#333', width: 0.5 } |
|
|
}, |
|
|
name: model, |
|
|
text: pts.map(p => `Entropy: ${p.entropy.toFixed(3)}<br>Coverage: ${(p.coverage * 100).toFixed(1)}%<br>Accuracy: ${p.accuracy.toFixed(1)}%`), |
|
|
hovertemplate: '<b>' + model + '</b><br>%{text}<extra></extra>', |
|
|
showlegend: false |
|
|
}; |
|
|
|
|
|
const layout = { |
|
|
...darkLayout, |
|
|
xaxis: { |
|
|
...darkLayout.xaxis, |
|
|
title: { text: 'Entropy', font: { size: 16, color: '#1d1d1f' } }, |
|
|
range: [0.6, 1.05], |
|
|
dtick: 0.1 |
|
|
}, |
|
|
yaxis: { |
|
|
...darkLayout.yaxis, |
|
|
title: { text: 'Coverage', font: { size: 16, color: '#1d1d1f' } }, |
|
|
range: [-0.05, yMax] |
|
|
}, |
|
|
margin: { t: 20, r: 20, b: 50, l: 50 } |
|
|
}; |
|
|
|
|
|
const chartDiv = document.getElementById(chartId); |
|
|
if (chartDiv) { |
|
|
|
|
|
chartDiv.style.transition = 'opacity 0.3s ease'; |
|
|
chartDiv.style.opacity = '0.3'; |
|
|
|
|
|
setTimeout(() => { |
|
|
|
|
|
Plotly.react(chartId, [trace], layout, plotlyConfig); |
|
|
|
|
|
|
|
|
chartDiv.style.opacity = '1'; |
|
|
|
|
|
|
|
|
addHoverHighlight(chartId); |
|
|
}, 150); |
|
|
} else { |
|
|
Plotly.newPlot(chartId, [trace], layout, plotlyConfig); |
|
|
|
|
|
setTimeout(() => addHoverHighlight(chartId), 50); |
|
|
} |
|
|
}); |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
document.addEventListener('DOMContentLoaded', () => { |
|
|
|
|
|
|
|
|
const sections = document.querySelectorAll('section.section'); |
|
|
sections.forEach(section => { |
|
|
lazyLoadObserver.observe(section); |
|
|
}); |
|
|
}); |
|
|
|
|
|
|
|
|
let resizeTimeout; |
|
|
const resizeHandler = throttle(() => { |
|
|
|
|
|
if (initializedCharts.has('scaling')) { |
|
|
['mimic', '10k', 'globem'].forEach(s => { |
|
|
const el = document.getElementById(`scaling-${s}`); |
|
|
if (el && el.data) Plotly.Plots.resize(el); |
|
|
}); |
|
|
} |
|
|
if (initializedCharts.has('ranking')) { |
|
|
['mimic', '10k', 'globem'].forEach(s => { |
|
|
const el = document.getElementById(`ranking-${s}`); |
|
|
if (el && el.data) Plotly.Plots.resize(el); |
|
|
}); |
|
|
} |
|
|
if (initializedCharts.has('turn')) { |
|
|
['mimic', '10k', 'globem'].forEach(s => { |
|
|
const el = document.getElementById(`turn-${s}`); |
|
|
if (el && el.data) Plotly.Plots.resize(el); |
|
|
}); |
|
|
} |
|
|
if (initializedCharts.has('probing')) { |
|
|
['mimic', '10k', 'globem'].forEach(s => { |
|
|
const el = document.getElementById(`probing-${s}`); |
|
|
if (el && el.data) Plotly.Plots.resize(el); |
|
|
}); |
|
|
} |
|
|
if (initializedCharts.has('entropy')) { |
|
|
for (let i = 0; i < 6; i++) { |
|
|
const el = document.getElementById(`entropy-model-${i}`); |
|
|
if (el && el.data) Plotly.Plots.resize(el); |
|
|
} |
|
|
} |
|
|
if (initializedCharts.has('error')) { |
|
|
const el = document.getElementById('error-chart'); |
|
|
if (el && el.data) Plotly.Plots.resize(el); |
|
|
} |
|
|
}, 250); |
|
|
|
|
|
window.addEventListener('resize', () => { |
|
|
clearTimeout(resizeTimeout); |
|
|
resizeTimeout = setTimeout(resizeHandler, 250); |
|
|
}); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function addHoverHighlight(chartId) { |
|
|
const chart = document.getElementById(chartId); |
|
|
if (!chart || !chart.on) return; |
|
|
|
|
|
let lastHoveredTrace = null; |
|
|
let lastHoveredPoint = null; |
|
|
let isAnimating = false; |
|
|
|
|
|
|
|
|
const handleHover = throttle(function (data) { |
|
|
if (!data || !data.points || !data.points[0]) return; |
|
|
|
|
|
const point = data.points[0]; |
|
|
const traceIndex = point.curveNumber; |
|
|
const pointIndex = point.pointNumber; |
|
|
|
|
|
|
|
|
if ((traceIndex === lastHoveredTrace && pointIndex === lastHoveredPoint) || isAnimating) return; |
|
|
|
|
|
lastHoveredTrace = traceIndex; |
|
|
lastHoveredPoint = pointIndex; |
|
|
isAnimating = true; |
|
|
|
|
|
|
|
|
const opacities = []; |
|
|
const markerSizes = []; |
|
|
const lineWidths = []; |
|
|
const traceIndices = []; |
|
|
|
|
|
const numTraces = chart.data?.length || 0; |
|
|
|
|
|
for (let i = 0; i < numTraces; i++) { |
|
|
const trace = chart.data[i]; |
|
|
if (!trace) continue; |
|
|
|
|
|
|
|
|
if (trace.fill === 'toself') continue; |
|
|
|
|
|
traceIndices.push(i); |
|
|
|
|
|
if (i === traceIndex) { |
|
|
opacities.push(1); |
|
|
lineWidths.push(4); |
|
|
const numPoints = trace.x?.length || 0; |
|
|
const sizes = Array(numPoints).fill(6); |
|
|
if (pointIndex < numPoints) sizes[pointIndex] = 12; |
|
|
markerSizes.push(sizes); |
|
|
} else { |
|
|
opacities.push(0.4); |
|
|
lineWidths.push(2); |
|
|
const numPoints = trace.x?.length || 0; |
|
|
markerSizes.push(Array(numPoints).fill(6)); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
requestAnimationFrame(() => { |
|
|
if (traceIndices.length > 0) { |
|
|
Plotly.restyle(chartId, { |
|
|
'opacity': opacities, |
|
|
'marker.size': markerSizes, |
|
|
'line.width': lineWidths |
|
|
}, traceIndices).then(() => { |
|
|
isAnimating = false; |
|
|
}).catch(() => { |
|
|
isAnimating = false; |
|
|
}); |
|
|
} else { |
|
|
isAnimating = false; |
|
|
} |
|
|
}); |
|
|
}, 50); |
|
|
|
|
|
chart.on('plotly_hover', handleHover); |
|
|
|
|
|
chart.on('plotly_unhover', function () { |
|
|
lastHoveredTrace = null; |
|
|
lastHoveredPoint = null; |
|
|
|
|
|
const numTraces = chart.data?.length || 0; |
|
|
if (numTraces === 0) return; |
|
|
|
|
|
|
|
|
const opacities = []; |
|
|
const markerSizes = []; |
|
|
const lineWidths = []; |
|
|
const traceIndices = []; |
|
|
|
|
|
for (let i = 0; i < numTraces; i++) { |
|
|
const trace = chart.data[i]; |
|
|
if (!trace) continue; |
|
|
|
|
|
|
|
|
if (trace.fill === 'toself') continue; |
|
|
|
|
|
traceIndices.push(i); |
|
|
opacities.push(1); |
|
|
lineWidths.push(2); |
|
|
const numPoints = trace.x?.length || 0; |
|
|
markerSizes.push(Array(numPoints).fill(6)); |
|
|
} |
|
|
|
|
|
|
|
|
if (traceIndices.length > 0) { |
|
|
requestAnimationFrame(() => { |
|
|
Plotly.restyle(chartId, { |
|
|
'opacity': opacities, |
|
|
'marker.size': markerSizes, |
|
|
'line.width': lineWidths |
|
|
}, traceIndices); |
|
|
}); |
|
|
} |
|
|
}); |
|
|
} |
|
|
|
|
|
|
|
|
function applyHoverEffectsForSection(sectionId) { |
|
|
requestAnimationFrame(() => { |
|
|
switch (sectionId) { |
|
|
case 'scaling': |
|
|
['mimic', '10k', 'globem'].forEach(s => addHoverHighlight(`scaling-${s}`)); |
|
|
break; |
|
|
case 'probing': |
|
|
['mimic', '10k', 'globem'].forEach(s => addHoverHighlight(`probing-${s}`)); |
|
|
break; |
|
|
case 'entropy': |
|
|
for (let i = 0; i < 6; i++) addHoverHighlight(`entropy-model-${i}`); |
|
|
break; |
|
|
} |
|
|
}); |
|
|
} |
|
|
|