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