Spaces:
Running
Running
| <html lang="en"> | |
| <head> | |
| <meta charset="utf-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1"> | |
| <title>Benchmark Leaderboard Race</title> | |
| <script src="https://d3js.org/d3.v7.min.js"></script> | |
| <style> | |
| @import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap'); | |
| * { margin: 0; padding: 0; box-sizing: border-box; } | |
| body { | |
| font-family: Inter, system-ui, -apple-system, sans-serif; | |
| background: #fafafa; | |
| color: #1a1a2e; | |
| } | |
| header { | |
| max-width: 1100px; | |
| margin: 0 auto; | |
| padding: 24px 24px 0; | |
| } | |
| header h1 { | |
| font-size: 24px; | |
| font-weight: 700; | |
| margin-bottom: 4px; | |
| } | |
| header p { | |
| font-size: 14px; | |
| color: #666; | |
| margin-bottom: 16px; | |
| } | |
| header a { color: #6366f1; text-decoration: none; } | |
| header a:hover { text-decoration: underline; } | |
| #controls { | |
| max-width: 1100px; | |
| margin: 0 auto; | |
| padding: 0 24px 16px; | |
| display: flex; | |
| gap: 16px; | |
| align-items: center; | |
| flex-wrap: wrap; | |
| } | |
| #controls label { | |
| font-size: 13px; | |
| font-weight: 500; | |
| color: #555; | |
| } | |
| #benchmark-select { | |
| font-family: inherit; | |
| font-size: 14px; | |
| padding: 6px 12px; | |
| border: 1px solid #ddd; | |
| border-radius: 6px; | |
| background: white; | |
| cursor: pointer; | |
| } | |
| .btn { | |
| font-family: inherit; | |
| font-size: 14px; | |
| font-weight: 600; | |
| padding: 7px 18px; | |
| border: none; | |
| border-radius: 6px; | |
| cursor: pointer; | |
| transition: background 0.15s; | |
| } | |
| #play-btn { | |
| background: #6366f1; | |
| color: white; | |
| } | |
| #play-btn:hover { background: #4f46e5; } | |
| #reset-btn { | |
| background: #e5e7eb; | |
| color: #374151; | |
| } | |
| #reset-btn:hover { background: #d1d5db; } | |
| .speed-group { | |
| display: flex; | |
| align-items: center; | |
| gap: 8px; | |
| } | |
| #speed-slider { | |
| width: 120px; | |
| accent-color: #6366f1; | |
| } | |
| #speed-label { | |
| font-size: 12px; | |
| color: #888; | |
| min-width: 40px; | |
| } | |
| #frame-info { | |
| font-size: 12px; | |
| color: #aaa; | |
| margin-left: auto; | |
| } | |
| #chart-container { | |
| max-width: 1100px; | |
| margin: 0 auto; | |
| padding: 0 24px 16px; | |
| } | |
| svg { | |
| width: 100%; | |
| height: auto; | |
| display: block; | |
| } | |
| .bar-name { | |
| font-weight: 600; | |
| fill: white; | |
| pointer-events: none; | |
| } | |
| .bar-name-link { | |
| cursor: default; | |
| pointer-events: none; | |
| } | |
| .clickable .bar-name-link { | |
| cursor: pointer; | |
| pointer-events: auto; | |
| } | |
| .clickable .bar-name-link:hover text { | |
| text-decoration: underline; | |
| fill: #eef; | |
| } | |
| .bar-score { | |
| font-weight: 500; | |
| fill: #333; | |
| pointer-events: none; | |
| } | |
| .date-watermark { | |
| fill: rgba(0, 0, 0, 0.06); | |
| font-size: 64px; | |
| font-weight: 700; | |
| pointer-events: none; | |
| font-family: Inter, system-ui, sans-serif; | |
| } | |
| .axis text { | |
| font-size: 11px; | |
| fill: #888; | |
| } | |
| .axis .domain { stroke: #ddd; } | |
| .axis .tick line { stroke: #eee; } | |
| </style> | |
| </head> | |
| <body> | |
| <header> | |
| <h1>Benchmark Leaderboard Race</h1> | |
| <p>Watch how AI model rankings evolve over time. Scores from | |
| <a href="https://huggingface.co/datasets?benchmark=benchmark:official&sort=trending">HuggingFace Dataset Leaderboards</a>, | |
| release dates from model repo metadata.</p> | |
| </header> | |
| <div id="controls"> | |
| <label for="benchmark-select">Benchmark:</label> | |
| <select id="benchmark-select"></select> | |
| <button class="btn" id="play-btn">▶ Play</button> | |
| <button class="btn" id="reset-btn">↺ Reset</button> | |
| <div class="speed-group"> | |
| <label for="speed-slider">Speed:</label> | |
| <input type="range" id="speed-slider" min="100" max="2000" value="600" step="50"> | |
| <span id="speed-label">600ms</span> | |
| </div> | |
| <span id="frame-info"></span> | |
| </div> | |
| <div id="chart-container"> | |
| <svg id="chart"></svg> | |
| </div> | |
| <script> | |
| (async function () { | |
| // ---- Load data ---- | |
| const raw = await d3.json("data.json"); | |
| const logos = raw.logos || {}; | |
| const colors = raw.colors || {}; | |
| // ---- Config ---- | |
| const topN = 15; | |
| const margin = { top: 30, right: 90, bottom: 10, left: 6 }; | |
| const width = 1050; | |
| // Fit all bars in viewport: subtract header/controls (~140px) from window height | |
| const availableHeight = Math.max(400, window.innerHeight - 160); | |
| const barHeight = Math.min(38, Math.floor((availableHeight - margin.top - margin.bottom) / topN)); | |
| const height = margin.top + barHeight * topN + margin.bottom; | |
| // ---- Populate dropdown ---- | |
| const select = d3.select("#benchmark-select"); | |
| const benchmarkKeys = Object.keys(raw.benchmarks); | |
| select.selectAll("option") | |
| .data(benchmarkKeys) | |
| .join("option") | |
| .attr("value", d => d) | |
| .text(d => raw.benchmarks[d].name); | |
| // Default to sweVerified if available | |
| if (benchmarkKeys.includes("sweVerified")) { | |
| select.property("value", "sweVerified"); | |
| } | |
| // ---- SVG setup ---- | |
| const svg = d3.select("#chart") | |
| .attr("viewBox", [0, 0, width, height]); | |
| const x = d3.scaleLinear().range([margin.left, width - margin.right]); | |
| const y = d3.scaleBand() | |
| .domain(d3.range(topN)) | |
| .range([margin.top, margin.top + barHeight * topN]) | |
| .padding(0.12); | |
| // Safe y position — returns off-screen bottom for ranks >= topN | |
| function yPos(rank) { | |
| if (rank >= topN) return margin.top + barHeight * topN; | |
| return y(rank) ?? margin.top + barHeight * topN; | |
| } | |
| // Groups (layered bottom to top) | |
| const watermarkG = svg.append("g"); | |
| const axisG = svg.append("g").attr("class", "axis"); | |
| const barG = svg.append("g"); | |
| const logoG = svg.append("g"); | |
| const nameG = svg.append("g"); | |
| const scoreG = svg.append("g"); | |
| // Dynamic sizes based on bar height | |
| const nameFontSize = Math.max(9, Math.min(13, barHeight * 0.36)); | |
| const scoreFontSize = Math.max(8, Math.min(12, barHeight * 0.33)); | |
| const watermarkSize = Math.max(36, Math.min(64, height * 0.11)); | |
| // Date watermark | |
| const ticker = watermarkG.append("text") | |
| .attr("class", "date-watermark") | |
| .style("font-size", watermarkSize + "px") | |
| .attr("x", width - margin.right - 10) | |
| .attr("y", height - 20) | |
| .attr("text-anchor", "end"); | |
| // ---- Build keyframes for a benchmark ---- | |
| function buildKeyframes(benchmarkKey) { | |
| const bm = raw.benchmarks[benchmarkKey]; | |
| if (!bm) return []; | |
| const models = bm.models | |
| .map(m => ({ ...m, dateObj: new Date(m.date) })) | |
| .sort((a, b) => a.dateObj - b.dateObj); | |
| const dateSet = [...new Set(models.map(m => m.date))]; | |
| // dateSet is already sorted because models were sorted | |
| const keyframes = []; | |
| const pool = new Map(); // model_id -> best entry | |
| for (const dateStr of dateSet) { | |
| const newModels = models.filter(m => m.date === dateStr); | |
| for (const m of newModels) { | |
| if (!pool.has(m.model_id) || m.score > pool.get(m.model_id).score) { | |
| pool.set(m.model_id, m); | |
| } | |
| } | |
| const ranked = [...pool.values()] | |
| .sort((a, b) => b.score - a.score) | |
| .slice(0, topN) | |
| .map((m, i) => ({ ...m, rank: i })); | |
| keyframes.push({ date: dateStr, ranked }); | |
| } | |
| return keyframes; | |
| } | |
| // ---- State ---- | |
| let keyframes = []; | |
| let currentFrame = 0; | |
| let playing = false; | |
| let frameDuration = 600; | |
| function getPrev(frameIdx) { | |
| if (frameIdx <= 0) return new Map(); | |
| const map = new Map(); | |
| for (const m of keyframes[frameIdx - 1].ranked) { | |
| map.set(m.model_id, m); | |
| } | |
| return map; | |
| } | |
| function getNext(frameIdx) { | |
| if (frameIdx >= keyframes.length - 1) return new Map(); | |
| const map = new Map(); | |
| for (const m of keyframes[frameIdx + 1].ranked) { | |
| map.set(m.model_id, m); | |
| } | |
| return map; | |
| } | |
| // ---- Render a single frame ---- | |
| function renderFrame(frameIdx, duration) { | |
| const kf = keyframes[frameIdx]; | |
| if (!kf) return Promise.resolve(); | |
| const { date, ranked } = kf; | |
| const prevMap = getPrev(frameIdx); | |
| const nextMap = getNext(frameIdx); | |
| const bw = y.bandwidth(); | |
| // Update x scale | |
| const maxScore = d3.max(ranked, d => d.score) || 1; | |
| x.domain([0, maxScore * 1.12]); | |
| // Axis | |
| const t = svg.transition().duration(duration).ease(d3.easeLinear); | |
| axisG.transition(t) | |
| .call(d3.axisTop(x).ticks(6).tickSize(-(height - margin.top - margin.bottom))) | |
| .attr("transform", `translate(0,${margin.top})`) | |
| .call(g => g.select(".domain").remove()); | |
| // Date watermark | |
| const dateObj = new Date(date); | |
| const dateLabel = dateObj.toLocaleDateString("en-US", { | |
| year: "numeric", month: "short", day: "numeric" | |
| }); | |
| ticker.text(dateLabel); | |
| // Frame info | |
| d3.select("#frame-info").text(`${frameIdx + 1} / ${keyframes.length}`); | |
| // Helper: get enter y (from previous frame or off-screen) | |
| function enterY(d) { | |
| const p = prevMap.get(d.model_id); | |
| return yPos(p ? p.rank : topN); | |
| } | |
| // Helper: get exit y (to next frame or off-screen) | |
| function exitY(d) { | |
| const n = nextMap.get(d.model_id); | |
| return yPos(n ? n.rank : topN); | |
| } | |
| // ---- Bars ---- | |
| const bars = barG.selectAll("rect").data(ranked, d => d.model_id); | |
| bars.join( | |
| enter => enter.append("rect") | |
| .attr("fill", d => colors[d.provider] || "#999") | |
| .attr("rx", 3) | |
| .attr("height", bw) | |
| .attr("x", x(0)) | |
| .attr("y", d => enterY(d)) | |
| .attr("width", d => { | |
| const p = prevMap.get(d.model_id); | |
| return Math.max(0, x(p ? p.score : 0) - x(0)); | |
| }) | |
| .call(enter => enter.transition(t) | |
| .attr("y", d => yPos(d.rank)) | |
| .attr("width", d => Math.max(0, x(d.score) - x(0))) | |
| ), | |
| update => update.call(update => update.transition(t) | |
| .attr("y", d => yPos(d.rank)) | |
| .attr("width", d => Math.max(0, x(d.score) - x(0))) | |
| ), | |
| exit => exit.call(exit => exit.transition(t) | |
| .attr("y", d => exitY(d)) | |
| .attr("width", d => { | |
| const n = nextMap.get(d.model_id); | |
| return Math.max(0, x(n ? n.score : 0) - x(0)); | |
| }) | |
| .remove() | |
| ) | |
| ); | |
| // ---- Model name labels (inside bar, wrapped in <a> for clickability) ---- | |
| const names = nameG.selectAll("a").data(ranked, d => d.model_id); | |
| names.join( | |
| enter => { | |
| const a = enter.append("a") | |
| .attr("class", "bar-name-link") | |
| .attr("href", d => `https://huggingface.co/${d.model_id}`) | |
| .attr("target", "_blank"); | |
| a.append("text") | |
| .attr("class", "bar-name") | |
| .style("font-size", nameFontSize + "px") | |
| .attr("x", x(0) + 8) | |
| .attr("y", d => enterY(d) + bw / 2) | |
| .attr("dy", "0.35em") | |
| .text(d => d.short_name); | |
| a.select("text").call(enter => enter.transition(t) | |
| .attr("y", d => yPos(d.rank) + bw / 2) | |
| ); | |
| return a; | |
| }, | |
| update => update.call(update => { | |
| update.attr("href", d => `https://huggingface.co/${d.model_id}`); | |
| update.select("text").transition(t) | |
| .attr("y", d => yPos(d.rank) + bw / 2); | |
| }), | |
| exit => exit.call(exit => { | |
| exit.select("text").transition(t) | |
| .attr("y", d => exitY(d) + bw / 2); | |
| exit.transition(t).remove(); | |
| }) | |
| ); | |
| // ---- Score labels (at end of bar) ---- | |
| const scores = scoreG.selectAll("text").data(ranked, d => d.model_id); | |
| scores.join( | |
| enter => enter.append("text") | |
| .attr("class", "bar-score") | |
| .style("font-size", scoreFontSize + "px") | |
| .attr("x", d => { | |
| const p = prevMap.get(d.model_id); | |
| return x(p ? p.score : 0) + 4; | |
| }) | |
| .attr("y", d => enterY(d) + bw / 2) | |
| .attr("dy", "0.35em") | |
| .text(d => d.score.toFixed(1)) | |
| .call(enter => enter.transition(t) | |
| .attr("x", d => x(d.score) + 4) | |
| .attr("y", d => yPos(d.rank) + bw / 2) | |
| .tween("text", function (d) { | |
| const p = prevMap.get(d.model_id); | |
| const i = d3.interpolateNumber(p ? p.score : 0, d.score); | |
| return function (t) { this.textContent = i(t).toFixed(1); }; | |
| }) | |
| ), | |
| update => update.call(update => update.transition(t) | |
| .attr("x", d => x(d.score) + 4) | |
| .attr("y", d => yPos(d.rank) + bw / 2) | |
| .tween("text", function (d) { | |
| const prevScore = parseFloat(this.textContent) || 0; | |
| const i = d3.interpolateNumber(prevScore, d.score); | |
| return function (t) { this.textContent = i(t).toFixed(1); }; | |
| }) | |
| ), | |
| exit => exit.call(exit => exit.transition(t) | |
| .attr("x", d => { | |
| const n = nextMap.get(d.model_id); | |
| return x(n ? n.score : 0) + 4; | |
| }) | |
| .attr("y", d => exitY(d) + bw / 2) | |
| .remove() | |
| ) | |
| ); | |
| // ---- Provider logos (at end of bar, after score) ---- | |
| const logoSize = bw * 0.65; | |
| const logosData = ranked.filter(d => logos[d.provider]); | |
| const logoEls = logoG.selectAll("image").data(logosData, d => d.model_id); | |
| logoEls.join( | |
| enter => enter.append("image") | |
| .attr("href", d => logos[d.provider]) | |
| .attr("width", logoSize) | |
| .attr("height", logoSize) | |
| .attr("x", d => { | |
| const p = prevMap.get(d.model_id); | |
| return x(p ? p.score : 0) + 40; | |
| }) | |
| .attr("y", d => enterY(d) + (bw - logoSize) / 2) | |
| .call(enter => enter.transition(t) | |
| .attr("x", d => x(d.score) + 40) | |
| .attr("y", d => yPos(d.rank) + (bw - logoSize) / 2) | |
| ), | |
| update => update.call(update => update.transition(t) | |
| .attr("x", d => x(d.score) + 40) | |
| .attr("y", d => yPos(d.rank) + (bw - logoSize) / 2) | |
| ), | |
| exit => exit.call(exit => exit.transition(t) | |
| .attr("x", d => { | |
| const n = nextMap.get(d.model_id); | |
| return x(n ? n.score : 0) + 40; | |
| }) | |
| .attr("y", d => exitY(d) + (bw - logoSize) / 2) | |
| .remove() | |
| ) | |
| ); | |
| return t.end().catch(() => {}); | |
| } | |
| // ---- Animation ---- | |
| function setClickable(enabled) { | |
| nameG.classed("clickable", enabled); | |
| } | |
| async function play() { | |
| if (playing) return; | |
| playing = true; | |
| setClickable(false); | |
| d3.select("#play-btn").html("▮▮ Pause"); | |
| for (let i = currentFrame; i < keyframes.length; i++) { | |
| if (!playing) break; | |
| currentFrame = i; | |
| await renderFrame(i, frameDuration); | |
| } | |
| playing = false; | |
| setClickable(true); | |
| d3.select("#play-btn").html("▶ Play"); | |
| } | |
| function pause() { | |
| playing = false; | |
| d3.select("#play-btn").html("▶ Play"); | |
| } | |
| function clearChart() { | |
| barG.selectAll("*").remove(); | |
| nameG.selectAll("*").remove(); | |
| scoreG.selectAll("*").remove(); | |
| logoG.selectAll("*").remove(); | |
| ticker.text(""); | |
| setClickable(false); | |
| } | |
| function reset() { | |
| pause(); | |
| currentFrame = 0; | |
| clearChart(); | |
| d3.select("#frame-info").text(""); | |
| if (keyframes.length > 0) { | |
| renderFrame(0, 0); | |
| } | |
| } | |
| function loadBenchmark(key) { | |
| pause(); | |
| keyframes = buildKeyframes(key); | |
| currentFrame = 0; | |
| clearChart(); | |
| if (keyframes.length > 0) { | |
| renderFrame(0, 0); | |
| } | |
| d3.select("#frame-info").text(keyframes.length > 0 ? `1 / ${keyframes.length}` : "No data"); | |
| } | |
| // ---- Event listeners ---- | |
| d3.select("#play-btn").on("click", () => { | |
| if (playing) { | |
| pause(); | |
| } else { | |
| if (currentFrame >= keyframes.length - 1) { | |
| currentFrame = 0; | |
| } | |
| play(); | |
| } | |
| }); | |
| d3.select("#reset-btn").on("click", reset); | |
| d3.select("#benchmark-select").on("change", function () { | |
| loadBenchmark(this.value); | |
| }); | |
| d3.select("#speed-slider").on("input", function () { | |
| frameDuration = +this.value; | |
| d3.select("#speed-label").text(this.value + "ms"); | |
| }); | |
| // ---- Initialize: load default benchmark and auto-play ---- | |
| const defaultKey = benchmarkKeys.includes("sweVerified") ? "sweVerified" : benchmarkKeys[0]; | |
| if (defaultKey) { | |
| loadBenchmark(defaultKey); | |
| // Auto-start animation after a brief delay for the page to settle | |
| setTimeout(() => play(), 400); | |
| } | |
| })(); | |
| </script> | |
| </body> | |
| </html> | |