joelniklaus HF Staff commited on
Commit
f838d6f
Β·
1 Parent(s): 663cec5

make verbosity analysis take data from rephrasing_metadata.json instead

Browse files
app/src/content/chapters/analyses.mdx CHANGED
@@ -75,5 +75,6 @@ Different prompt formats produce wildly different output lengths. <FigRef target
75
  <HtmlEmbed
76
  id="verbosity"
77
  src="verbosity.html"
 
78
  desc="Output tokens per document across prompt types and model families. Hover over dots to see detailed statistics for each experiment."
79
  />
 
75
  <HtmlEmbed
76
  id="verbosity"
77
  src="verbosity.html"
78
+ data="rephrasing_metadata.json"
79
  desc="Output tokens per document across prompt types and model families. Hover over dots to see detailed statistics for each experiment."
80
  />
app/src/content/embeds/verbosity.html CHANGED
@@ -1,4 +1,4 @@
1
- <div class="d3-synth-banner" style="width:100%;margin:10px 0;aspect-ratio:3/1;min-height:260px;"></div>
2
  <script>
3
  (() => {
4
  const ensureD3 = (cb) => {
@@ -16,487 +16,470 @@
16
  };
17
 
18
  const bootstrap = () => {
19
- const mount = document.currentScript ? document.currentScript.previousElementSibling : null;
20
- const container = (mount && mount.querySelector && mount.querySelector('.d3-synth-banner')) ||
21
- Array.from(document.querySelectorAll('.d3-synth-banner')).find(el => el.dataset.mounted !== 'true');
22
- if (!container) return;
23
- if (container.dataset) {
24
- if (container.dataset.mounted === 'true') return;
25
- container.dataset.mounted = 'true';
26
  }
27
-
28
- // ═══════════════════════════════════════════════════════════════
29
- // DATA β€” 65 rephrasing experiments
30
- // [cat, prompt, model, source, compTokensB, promptTokensB,
31
- // numDocsM, dclmDiff, outDclm, inDclm, eduDiff, outEdu, inEdu]
32
- // ═══════════════════════════════════════════════════════════════
33
- const raw = [
34
- ['Format','Article','Gemma-3 1B','FW-Edu HQ',4.00,5.66,19.7,-0.018,0.080,0.098,-0.432,1.484,1.679],
35
- ['Format','Commentary','Qwen3 1.7B','FW-Edu HQ',1.64,5.94,20.1,0.630,0.847,0.217,-0.229,3.533,3.762],
36
- ['Format','Commentary','Gemma-3 1B','DCLM',1.46,5.52,29.5,0.202,0.344,0.142,0.359,1.943,1.584],
37
- ['Format','Commentary','Gemma-3 1B','FW-Edu HQ',0.64,2.42,20.1,0.055,0.109,0.054,-0.517,0.793,0.921],
38
- ['Format','Discussion','Qwen3 1.7B','FW-Edu HQ',2.90,7.20,20.1,0.715,0.932,0.217,-0.122,3.640,3.762],
39
- ['Format','Discussion','Gemma-3 1B','FW-Edu HQ',3.96,6.25,20.1,0.036,0.161,0.125,-0.574,1.862,2.197],
40
- ['Format','FAQ','Gemma-3 12B','FW-Edu HQ',3.22,4.06,20.1,0.712,0.930,0.217,-0.295,3.467,3.762],
41
- ['Format','FAQ','Gemma-3 12B','FW-Edu LQ',1.91,1.90,32.9,0.799,0.828,0.029,0.357,1.263,0.906],
42
- ['Format','FAQ','Gemma-3 1B','Cosmopedia',1.33,1.31,31.1,0.329,0.906,0.578,-0.264,2.226,2.490],
43
- ['Format','FAQ','Gemma-3 1B','DCLM',1.52,2.08,29.5,0.709,0.851,0.142,0.346,1.930,1.585],
44
- ['Format','FAQ','Gemma-3 1B','FW-Edu HQ',17.67,22.21,20.1,0.711,0.929,0.218,-0.346,3.416,3.762],
45
- ['Format','FAQ','Gemma-3 1B','FW-Edu LQ',1.84,1.75,32.9,0.690,0.719,0.029,0.369,1.275,0.905],
46
- ['Format','FAQ','Falcon3 1B','FW-Edu HQ',3.41,4.84,20.1,0.691,0.909,0.217,-0.249,3.512,3.762],
47
- ['Format','FAQ','Granite3 1B','FW-Edu HQ',2.55,4.17,20.1,0.680,0.898,0.218,-0.166,3.596,3.762],
48
- ['Format','FAQ','Llama-3.2 1B','FW-Edu HQ',6.29,11.62,20.1,0.735,0.952,0.217,-0.375,3.387,3.762],
49
- ['Format','FAQ','Qwen3 1.7B','FW-Edu HQ',2.88,4.77,20.1,0.632,0.850,0.218,-0.219,3.543,3.762],
50
- ['Format','FAQ','SmolLM2 1.7B','FW-Edu HQ',0.89,2.00,20.1,0.648,0.867,0.219,-0.406,3.355,3.762],
51
- ['Format','Math','Gemma-3 12B','FW-Edu HQ',2.51,5.93,20.1,0.755,0.972,0.217,-0.370,3.392,3.762],
52
- ['Format','Math','Gemma-3 1B','FW-Edu HQ',2.46,5.08,20.1,0.620,0.838,0.218,-0.174,3.587,3.762],
53
- ['Format','Math','Gemma-3 270M','FW-Edu HQ',1.21,1.33,20.1,0.600,0.818,0.218,-0.406,3.357,3.762],
54
- ['Format','Math','Gemma-3 27B','FW-Edu HQ',1.05,2.62,20.1,0.754,0.971,0.217,-0.429,3.333,3.762],
55
- ['Format','Math','Gemma-3 4B','FW-Edu HQ',0.68,1.75,20.1,0.743,0.960,0.217,-0.335,3.427,3.762],
56
- ['Format','Math','Falcon3 1B','FW-Edu HQ',1.42,2.17,20.1,0.704,0.921,0.217,-0.156,3.606,3.762],
57
- ['Format','Math','Granite3 1B','FW-Edu HQ',1.57,3.35,20.1,0.698,0.916,0.218,-0.324,3.438,3.762],
58
- ['Format','Math','Llama-3.2 1B','FW-Edu HQ',0.86,2.17,20.1,0.737,0.955,0.218,-0.384,3.378,3.762],
59
- ['Format','Math','Qwen3 1.7B','FW-Edu HQ',0.56,2.00,20.1,0.742,0.959,0.218,-0.310,3.452,3.762],
60
- ['Format','Math','SmolLM2 1.7B','FW-Edu HQ',0.64,2.28,20.1,0.657,0.875,0.218,-0.669,3.093,3.762],
61
- ['Format','Table','Gemma-3 12B','FW-Edu HQ',0.76,2.49,20.1,0.719,0.937,0.217,-0.774,2.988,3.762],
62
- ['Format','Table','Gemma-3 1B','FW-Edu HQ',1.68,5.62,20.1,0.715,0.932,0.217,-0.776,2.985,3.762],
63
- ['Format','Table','Falcon3 1B','FW-Edu HQ',0.65,2.00,20.1,0.722,0.940,0.218,-0.748,3.015,3.763],
64
- ['Format','Table','Granite3 1B','FW-Edu HQ',1.44,3.90,20.1,0.726,0.944,0.218,-0.597,3.165,3.762],
65
- ['Format','Table','Llama-3.2 1B','FW-Edu HQ',0.88,2.73,20.1,0.772,0.989,0.217,-0.815,2.947,3.762],
66
- ['Format','Table','Qwen3 1.7B','FW-Edu HQ',0.80,2.69,20.1,0.751,0.969,0.218,-0.638,3.123,3.762],
67
- ['Format','Table','SmolLM2 1.7B','FW-Edu HQ',0.41,1.52,20.1,0.668,0.885,0.218,-0.943,2.818,3.762],
68
- ['Format','Tutorial','Gemma-3 12B','FW-Edu HQ',5.33,6.57,20.1,0.398,0.553,0.155,-0.077,2.612,2.667],
69
- ['Format','Tutorial','Gemma-3 12B','FW-Edu LQ',2.35,2.57,32.9,0.189,0.210,0.020,0.379,0.869,0.613],
70
- ['Format','Tutorial','Gemma-3 1B','Cosmopedia',1.02,1.47,31.1,-0.049,0.529,0.578,-0.080,2.415,2.495],
71
- ['Format','Tutorial','Gemma-3 1B','DCLM',1.67,3.30,29.5,0.371,0.514,0.142,0.528,2.113,1.585],
72
- ['Format','Tutorial','Gemma-3 1B','FW-Edu HQ',1.51,2.70,19.4,0.074,0.112,0.038,-0.077,0.632,0.646],
73
- ['Format','Tutorial','Gemma-3 1B','FW-Edu LQ',1.90,2.51,32.9,0.172,0.193,0.021,0.413,0.917,0.630],
74
- ['Format','Tutorial','Gemma-3 270M','FW-Edu HQ',1.64,1.71,20.1,0.514,0.731,0.217,-0.337,3.425,3.762],
75
- ['Format','Tutorial','Gemma-3 27B','FW-Edu HQ',2.55,3.29,20.1,0.485,0.702,0.218,-0.057,3.705,3.762],
76
- ['Format','Tutorial','Gemma-3 4B','FW-Edu HQ',1.50,2.02,20.1,0.404,0.621,0.217,-0.049,3.713,3.762],
77
- ['Format','Tutorial','Falcon3 1B','FW-Edu HQ',4.97,7.58,20.1,0.447,0.665,0.218,-0.145,3.617,3.762],
78
- ['Format','Tutorial','Granite3 1B','FW-Edu HQ',3.51,6.42,20.1,0.449,0.666,0.218,-0.072,3.690,3.762],
79
- ['Format','Tutorial','Llama-3.2 1B','FW-Edu HQ',2.36,4.88,20.1,0.557,0.774,0.217,-0.031,3.731,3.762],
80
- ['Format','Tutorial','Qwen1.5 1.8B','FW-Edu HQ',2.22,2.40,20.1,0.647,0.864,0.218,0.072,3.834,3.762],
81
- ['Format','Tutorial','Qwen2 1.5B','FW-Edu HQ',3.09,8.00,20.1,0.527,0.746,0.218,-0.010,3.751,3.762],
82
- ['Format','Tutorial','Qwen2.5 1.5B','FW-Edu HQ',2.27,4.87,20.1,0.576,0.794,0.218,-0.083,3.678,3.762],
83
- ['Format','Tutorial','Qwen3 1.7B','FW-Edu HQ',3.56,5.69,20.1,0.281,0.497,0.217,0.056,3.818,3.762],
84
- ['Format','Tutorial','SmolLM2 1.7B','FW-Edu HQ',1.52,3.38,20.1,0.439,0.658,0.219,-0.244,3.518,3.762],
85
- ['Nemotron','Distill','Gemma-3 1B','FW-Edu HQ',2.12,5.37,20.1,0.331,0.549,0.217,-0.132,3.629,3.762],
86
- ['Nemotron','Diverse QA','Gemma-3 1B','FW-Edu HQ',1.97,7.01,20.1,0.779,0.996,0.217,-0.208,3.554,3.762],
87
- ['Nemotron','Extract Knowledge','Gemma-3 1B','FW-Edu HQ',2.41,6.59,20.1,0.254,0.472,0.218,0.025,3.787,3.762],
88
- ['Nemotron','Knowledge List','Gemma-3 1B','FW-Edu HQ',2.90,9.15,20.1,0.589,0.806,0.217,-0.193,3.569,3.762],
89
- ['Nemotron','Wikipedia Style','Gemma-3 1B','FW-Edu HQ',3.43,8.37,20.1,0.297,0.515,0.217,-0.193,3.568,3.762],
90
- ['REWIRE','Guided Rewrite+','Gemma-3 12B','FW-Edu HQ',1.45,2.14,20.1,0.222,0.439,0.217,-0.102,3.660,3.762],
91
- ['REWIRE','Guided Rewrite+','Gemma-3 1B','FW-Edu HQ',1.63,3.42,20.1,0.097,0.315,0.217,-0.428,3.334,3.762],
92
- ['REWIRE','Guided Rewrite','Gemma-3 12B','FW-Edu HQ',1.78,1.72,20.1,0.607,0.824,0.217,-1.214,2.547,3.762],
93
- ['REWIRE','Guided Rewrite','Gemma-3 1B','FW-Edu HQ',8.69,10.30,20.1,0.278,0.495,0.217,-1.012,2.750,3.762],
94
- ['REWIRE','Guided Rewrite','Gemma-3 270M','FW-Edu HQ',0.78,1.71,20.1,0.755,0.972,0.217,-2.407,1.355,3.762],
95
- ['REWIRE','Guided Rewrite','Gemma-3 4B','FW-Edu HQ',0.91,1.10,20.1,0.436,0.654,0.218,-0.919,2.843,3.762],
96
- ['REWIRE','Guided Rewrite','Gemma-3 27B','FW-Edu HQ',8.43,8.64,19.95,0.546,0.763,0.217,-1.228,2.533,3.762],
97
- ['Format','Tutorial','SmolLM2 135M','FW-Edu HQ',4.51,6.99,20.1,0.423,0.641,0.218,-0.572,3.190,3.762],
98
- ['Format','Tutorial','SmolLM2 360M','FW-Edu HQ',3.14,6.52,20.1,0.441,0.659,0.218,-0.565,3.197,3.762]
99
- ];
100
-
101
- const experiments = raw.map((r, i) => ({
102
- idx: i, cat: r[0], prompt: r[1], model: r[2], source: r[3],
103
- compTokens: r[4], promptTokens: r[5], numDocs: r[6],
104
- dclmDiff: r[7], outDclm: r[8], inDclm: r[9],
105
- eduDiff: r[10], outEdu: r[11], inEdu: r[12],
106
- compPerDoc: r[4] * 1000 / r[6],
107
- phase: Math.random() * Math.PI * 2
108
- }));
109
-
110
- // ════════════════════════════════════════════════════���══════
111
- // MODEL FAMILIES
112
- // ═══════════════════════════════════════════════════════════
113
- const getFamily = (m) => {
114
- if (m.includes('SmolLM')) return 'SmolLM2';
115
- if (m.includes('Gemma')) return 'Gemma';
116
- if (m.includes('Qwen')) return 'Qwen';
117
- if (m.includes('Falcon')) return 'Falcon';
118
- if (m.includes('Granite')) return 'Granite';
119
- if (m.includes('Llama')) return 'Llama';
120
- return 'Other';
121
- };
122
- const familyColors = {
123
- 'Gemma': '#4EA5B7',
124
- 'Qwen': '#8B7BE8',
125
- 'SmolLM2': '#E8C44A',
126
- 'Falcon': '#E889AB',
127
- 'Granite': '#5BC0A4',
128
- 'Llama': '#D09090',
129
  };
130
- const familyOrder = ['Gemma','Qwen','SmolLM2','Falcon','Granite','Llama'];
131
- experiments.forEach(d => { d.family = getFamily(d.model); });
132
-
133
- // ═══════════════════════════════════════════════════════════
134
- // FILTER: Only prompts tested across 6 model families
135
- // (keep multi-source Gemma variants for Tutorial/FAQ)
136
- // ═══════════════════════════════════════════════════════════
137
- const focusPrompts = ['Math', 'Table', 'FAQ', 'Tutorial'];
138
- const filtered = experiments.filter(d => focusPrompts.includes(d.prompt));
139
-
140
- // Sort rows by median compPerDoc (most verbose on top)
141
- const promptStats = {};
142
- focusPrompts.forEach(p => {
143
- const vals = filtered.filter(d => d.prompt === p).map(d => d.compPerDoc).sort((a,b) => a - b);
144
- const mid = Math.floor(vals.length / 2);
145
- promptStats[p] = {
146
- median: vals.length % 2 ? vals[mid] : (vals[mid-1] + vals[mid]) / 2,
147
- count: vals.length
148
- };
149
- });
150
- const sortedPrompts = [...focusPrompts].sort((a, b) => promptStats[b].median - promptStats[a].median);
151
-
152
- // ═══════════════════════════════════════════════════════════
153
- // TOOLTIP
154
- // ═══════════════════════════════════════════════════════════
155
- const fmtB = (v) => v >= 10 ? v.toFixed(0) + 'B' : v.toFixed(1) + 'B';
156
- const fmtSign = (v, p) => (v >= 0 ? '+' : '') + v.toFixed(p || 3);
157
- const dCol = (v) => v >= 0 ? '#5BC0A4' : '#E889AB';
158
- const buildTip = (d) =>
159
- `<div style="font-weight:800;font-size:13px;">${d.prompt}</div>` +
160
- `<div style="font-size:11px;color:var(--muted-color);margin-top:-2px;margin-bottom:4px;">` +
161
- `<span style="display:inline-block;width:8px;height:8px;border-radius:50%;background:${familyColors[d.family]};margin-right:4px;vertical-align:middle;"></span>` +
162
- `${d.model} \u00b7 ${d.source}</div>` +
163
- `<div style="padding-top:5px;border-top:1px solid var(--border-color);display:grid;grid-template-columns:auto 1fr;gap:2px 8px;font-size:11.5px;">` +
164
- `<span style="color:var(--muted-color);">Total output</span><span>${fmtB(d.compTokens)}</span>` +
165
- `<span style="color:var(--muted-color);">Output/doc</span><span>${Math.round(d.compPerDoc)} tokens</span>` +
166
- `<span style="color:var(--muted-color);">Docs</span><span>${d.numDocs.toFixed(1)}M</span>` +
167
- `</div>` +
168
- `<div style="margin-top:5px;padding-top:5px;border-top:1px solid var(--border-color);display:grid;grid-template-columns:auto 1fr;gap:2px 8px;font-size:11.5px;">` +
169
- `<span style="color:var(--muted-color);">DCLM</span>` +
170
- `<span>${d.inDclm.toFixed(3)} \u2192 ${d.outDclm.toFixed(3)} <b style="color:${dCol(d.dclmDiff)};">${fmtSign(d.dclmDiff)}</b></span>` +
171
- `<span style="color:var(--muted-color);">Edu</span>` +
172
- `<span>${d.inEdu.toFixed(2)} \u2192 ${d.outEdu.toFixed(2)} <b style="color:${dCol(d.eduDiff)};">${fmtSign(d.eduDiff, 2)}</b></span>` +
173
- `</div>`;
174
-
175
- // ═══════════════════════════════════════════════════════════
176
- // SVG
177
- // ═══════════════════════════════════════════════════════════
178
- const svg = d3.select(container).append('svg')
179
- .attr('width', '100%')
180
- .style('display', 'block')
181
- .style('cursor', 'crosshair');
182
-
183
- let animFrame = null;
184
-
185
- const render = () => {
186
- const width = container.clientWidth || 800;
187
- const height = Math.max(260, Math.round(width / 3));
188
- svg.attr('width', width).attr('height', height);
189
-
190
- const isDark = document.documentElement.getAttribute('data-theme') === 'dark';
191
- const textColor = isDark ? 'rgba(255,255,255,0.72)' : 'rgba(0,0,0,0.62)';
192
- const mutedText = isDark ? 'rgba(255,255,255,0.22)' : 'rgba(0,0,0,0.18)';
193
- const subtleText = isDark ? 'rgba(255,255,255,0.12)' : 'rgba(0,0,0,0.08)';
194
- const refLine = isDark ? 'rgba(255,255,255,0.10)' : 'rgba(0,0,0,0.07)';
195
- const bandEven = isDark ? 'rgba(255,255,255,0.022)' : 'rgba(0,0,0,0.018)';
196
- const glowColor = isDark ? 'rgba(255,255,255,0.35)' : 'rgba(0,0,0,0.25)';
197
- const smolHighlight = isDark ? 'rgba(232,196,74,0.18)' : 'rgba(232,196,74,0.12)';
198
-
199
- const fontSize = Math.max(8, Math.min(12, width / 80));
200
-
201
- // ─── LAYOUT ───
202
- const labelW = Math.max(68, width * 0.085);
203
- const pl = labelW + 12;
204
- const pr = width * 0.965;
205
- const pt = height * 0.20;
206
- const pb = height * 0.86;
207
- const plotH = pb - pt;
208
- const rowH = plotH / sortedPrompts.length;
209
-
210
- const rowCenter = {};
211
- sortedPrompts.forEach((p, i) => { rowCenter[p] = pt + (i + 0.5) * rowH; });
212
-
213
- // ─── X SCALE: completion tokens per document (log) ───
214
- const xScale = d3.scaleLog().base(10)
215
- .domain([15, 1000])
216
- .range([pl, pr]);
217
-
218
- const rBase = Math.max(3.5, Math.min(8, width * 0.007));
219
-
220
- // ─── FORCE SIMULATION ───
221
- filtered.forEach(d => {
222
- d.tx = xScale(Math.max(15, d.compPerDoc));
223
- d.ty = rowCenter[d.prompt];
224
- d.x = d.tx + (Math.random() - 0.5) * 6;
225
- d.y = d.ty + (Math.random() - 0.5) * rowH * 0.3;
226
- });
227
 
228
- const sim = d3.forceSimulation(filtered)
229
- .force('x', d3.forceX(d => d.tx).strength(0.72))
230
- .force('y', d3.forceY(d => d.ty).strength(0.55))
231
- .force('collide', d3.forceCollide(rBase + 1.5).iterations(5))
232
- .stop();
233
- for (let i = 0; i < 250; i++) sim.tick();
234
-
235
- filtered.forEach(d => {
236
- d.x = Math.max(pl + rBase, Math.min(pr - rBase, d.x));
237
- const yMin = rowCenter[d.prompt] - rowH * 0.46;
238
- const yMax = rowCenter[d.prompt] + rowH * 0.46;
239
- d.y = Math.max(yMin, Math.min(yMax, d.y));
240
- });
241
 
242
- // ─── BG STARS ───
243
- const bgCol = (() => {
244
- const i01 = d3.interpolateRgb(d3.rgb(78,165,183), d3.rgb(206,192,250));
245
- const i12 = d3.interpolateRgb(d3.rgb(206,192,250), d3.rgb(232,137,171));
246
- return (v) => { const t = Math.max(0, Math.min(1, v)); return t <= 0.5 ? i01(t / 0.5) : i12((t - 0.5) / 0.5); };
247
- })();
248
- const gBg = svg.selectAll('g.bg').data([0]).join('g').attr('class', 'bg');
249
- if (!gBg.selectAll('circle').size()) {
250
- const stars = d3.range(50).map(() => ({
251
- x: Math.random() * width, y: Math.random() * height,
252
- z: Math.random(), r: 0.3 + Math.random() * 0.6
253
- }));
254
- gBg.selectAll('circle').data(stars).join('circle')
255
- .attr('cx', d => d.x).attr('cy', d => d.y).attr('r', d => d.r)
256
- .attr('fill', d => bgCol(d.z)).attr('fill-opacity', d => 0.03 + d.z * 0.04);
257
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
258
 
259
- // ─── ROW BANDS & LABELS ───
260
- const gRows = svg.selectAll('g.rows').data([0]).join('g').attr('class', 'rows');
261
- gRows.selectAll('*').remove();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
262
 
263
- sortedPrompts.forEach((p, i) => {
264
- const y = rowCenter[p];
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
265
 
266
- if (i % 2 === 0) {
267
- gRows.append('rect')
268
- .attr('x', pl - 6).attr('y', y - rowH / 2)
269
- .attr('width', pr - pl + 12).attr('height', rowH)
270
- .attr('fill', bandEven).attr('rx', 4);
271
- }
272
- if (i > 0) {
273
- gRows.append('line')
274
- .attr('x1', pl - 2).attr('x2', pr + 2)
275
- .attr('y1', y - rowH / 2).attr('y2', y - rowH / 2)
276
- .attr('stroke', refLine).attr('stroke-width', 0.5);
277
- }
 
278
 
279
- // SmolLM2 highlight zone β€” show where SmolLM2 dots land
280
- const smolExp = filtered.filter(d => d.prompt === p && d.family === 'SmolLM2');
281
- if (smolExp.length) {
282
- const smolX = smolExp[0].x;
283
- gRows.append('rect')
284
- .attr('x', smolX - rBase * 2.5).attr('y', y - rowH * 0.42)
285
- .attr('width', rBase * 5).attr('height', rowH * 0.84)
286
- .attr('fill', smolHighlight).attr('rx', rBase * 2);
 
 
 
 
 
 
 
287
  }
288
 
289
- // Label
290
- gRows.append('text')
291
- .attr('x', pl - 10).attr('y', y)
292
- .attr('text-anchor', 'end')
293
- .attr('dominant-baseline', 'central')
294
- .attr('fill', textColor)
295
- .attr('font-size', fontSize + 'px')
296
- .attr('font-weight', '700')
297
- .attr('font-family', 'system-ui, -apple-system, sans-serif')
298
- .attr('letter-spacing', '-0.2px')
299
- .text(p);
300
- });
301
-
302
- // ─── X AXIS TICKS ───
303
- const gRef = svg.selectAll('g.ref').data([0]).join('g').attr('class', 'ref');
304
- gRef.selectAll('*').remove();
305
-
306
- [20, 50, 100, 200, 500].forEach(v => {
307
- const x = xScale(v);
308
- if (x > pl && x < pr) {
309
- gRef.append('line')
310
- .attr('x1', x).attr('x2', x)
311
- .attr('y1', pb).attr('y2', pb + 3)
312
- .attr('stroke', mutedText).attr('stroke-width', 0.7);
313
- gRef.append('text')
314
- .attr('x', x).attr('y', pb + 5)
315
- .attr('text-anchor', 'middle')
316
- .attr('dominant-baseline', 'hanging')
317
- .attr('fill', mutedText)
318
- .attr('font-size', (fontSize * 0.65) + 'px')
 
 
 
 
 
 
 
 
319
  .attr('font-family', 'system-ui, -apple-system, sans-serif')
320
- .text(v);
321
- // Subtle grid line
322
- gRef.append('line')
323
- .attr('x1', x).attr('x2', x)
324
- .attr('y1', pt).attr('y2', pb)
325
- .attr('stroke', subtleText).attr('stroke-width', 0.4)
326
- .attr('stroke-dasharray', '2,6');
327
- }
328
- });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
329
 
330
- // Axis label
331
- gRef.append('text')
332
- .attr('x', pr).attr('y', pb + 16)
333
- .attr('text-anchor', 'end')
334
- .attr('fill', mutedText)
335
- .attr('font-size', (fontSize * 0.72) + 'px')
336
- .attr('font-weight', '600')
337
- .attr('font-family', 'system-ui, -apple-system, sans-serif')
338
- .text('Output tokens / document \u2192');
339
-
340
- // "Less verbose" / "More verbose" labels
341
- gRef.append('text')
342
- .attr('x', pl + 6).attr('y', pt - 5)
343
- .attr('text-anchor', 'start')
344
- .attr('fill', subtleText)
345
- .attr('font-size', (fontSize * 0.62) + 'px')
346
- .attr('font-style', 'italic')
347
- .attr('font-family', 'system-ui, -apple-system, sans-serif')
348
- .text('\u2190 concise');
349
-
350
- gRef.append('text')
351
- .attr('x', pr - 6).attr('y', pt - 5)
352
- .attr('text-anchor', 'end')
353
- .attr('fill', subtleText)
354
- .attr('font-size', (fontSize * 0.62) + 'px')
355
- .attr('font-style', 'italic')
356
- .attr('font-family', 'system-ui, -apple-system, sans-serif')
357
- .text('verbose \u2192');
358
-
359
- // ─── MODEL FAMILY LEGEND ───
360
- const gLeg = svg.selectAll('g.legend').data([0]).join('g').attr('class', 'legend');
361
- gLeg.selectAll('*').remove();
362
- const legDotR = Math.max(3, fontSize * 0.35);
363
- const legGap = Math.max(68, width * 0.082);
364
- const totalLegW = familyOrder.length * legGap;
365
- const legStartX = Math.max(pl, (width - totalLegW) / 2);
366
- const legY = height * 0.055;
367
-
368
- familyOrder.forEach((fam, i) => {
369
- const lx = legStartX + i * legGap;
370
- gLeg.append('circle')
371
- .attr('cx', lx).attr('cy', legY)
372
- .attr('r', legDotR)
373
- .attr('fill', familyColors[fam]).attr('fill-opacity', 0.85);
374
- gLeg.append('text')
375
- .attr('x', lx + legDotR + 4).attr('y', legY)
376
- .attr('dominant-baseline', 'central')
377
- .attr('fill', isDark ? 'rgba(255,255,255,0.48)' : 'rgba(0,0,0,0.42)')
378
  .attr('font-size', (fontSize * 0.72) + 'px')
379
- .attr('font-weight', fam === 'SmolLM2' ? '700' : '500')
380
  .attr('font-family', 'system-ui, -apple-system, sans-serif')
381
- .text(fam);
382
- });
 
 
 
 
 
 
 
 
 
383
 
384
- // ─── TOOLTIP ───
385
- container.style.position = container.style.position || 'relative';
386
- let tip = container.querySelector('.d3-tooltip');
387
- let tipInner;
388
- if (!tip) {
389
- tip = document.createElement('div');
390
- tip.className = 'd3-tooltip';
391
- Object.assign(tip.style, {
392
- position: 'absolute', top: '0px', left: '0px',
393
- transform: 'translate(-9999px, -9999px)',
394
- pointerEvents: 'none',
395
- padding: '10px 14px', borderRadius: '12px',
396
- fontSize: '12px', lineHeight: '1.4',
397
- border: '1px solid var(--border-color)',
398
- background: 'var(--surface-bg)',
399
- color: 'var(--text-color)',
400
- boxShadow: '0 8px 32px rgba(0,0,0,.28), 0 2px 8px rgba(0,0,0,.12)',
401
- opacity: '0', transition: 'opacity .12s ease',
402
- backdropFilter: 'saturate(1.12) blur(8px)',
403
- zIndex: '20', maxWidth: '320px'
404
- });
405
- tipInner = document.createElement('div');
406
- tipInner.className = 'd3-tooltip__inner';
407
- Object.assign(tipInner.style, {
408
- textAlign: 'left', display: 'flex', flexDirection: 'column',
409
- gap: '4px', minWidth: '220px'
 
 
 
 
 
 
410
  });
411
- tip.appendChild(tipInner);
412
- container.appendChild(tip);
413
- } else {
414
- tipInner = tip.querySelector('.d3-tooltip__inner') || tip;
415
- }
416
 
417
- // ─── DOTS ───
418
- const gDots = svg.selectAll('g.dots').data([0]).join('g').attr('class', 'dots');
419
-
420
- const handleEnter = function (ev, d) {
421
- gDots.selectAll('circle').attr('fill-opacity', 0.12).attr('stroke-opacity', 0.05);
422
- // Highlight same family across all prompts
423
- gDots.selectAll('circle')
424
- .filter(c => c.family === d.family)
425
- .attr('fill-opacity', 0.60).attr('stroke-opacity', 0.4);
426
- d3.select(this)
427
- .raise()
428
- .attr('fill-opacity', 1)
429
- .attr('stroke-opacity', 1)
430
- .style('filter', `drop-shadow(0 0 8px ${glowColor})`)
431
- .transition().duration(90).ease(d3.easeCubicOut)
432
- .attr('r', rBase * 1.5);
433
- tipInner.innerHTML = buildTip(d);
434
- tip.style.opacity = '1';
435
- };
436
- const handleMove = (ev) => {
437
- const [mx, my] = d3.pointer(ev, container);
438
- const bw = tip.offsetWidth || 260;
439
- const bh = tip.offsetHeight || 180;
440
- const ox = (mx + bw + 20 > width) ? -(bw + 12) : 12;
441
- const oy = (my + bh + 20 > height) ? -(bh + 12) : 14;
442
- tip.style.transform = `translate(${Math.round(mx + ox)}px, ${Math.round(my + oy)}px)`;
443
- };
444
- const handleLeave = function (ev, d) {
445
- gDots.selectAll('circle')
446
- .attr('fill-opacity', 0.82)
447
- .attr('stroke-opacity', 0.30);
448
- d3.select(this)
449
- .style('filter', null)
450
- .transition().duration(90).ease(d3.easeCubicOut)
451
- .attr('r', rBase);
452
- tip.style.opacity = '0';
453
- tip.style.transform = 'translate(-9999px, -9999px)';
454
- };
455
 
456
- gDots.selectAll('circle').data(filtered, d => d.idx)
457
- .join(
458
- enter => enter.append('circle')
459
- .attr('cx', d => d.x).attr('cy', d => d.y)
460
- .attr('r', rBase)
461
- .attr('fill', d => familyColors[d.family] || '#aaa')
462
- .attr('fill-opacity', 0.82)
463
- .attr('stroke', d => d.family === 'SmolLM2' ? '#fff' : (familyColors[d.family] || '#aaa'))
464
- .attr('stroke-width', d => d.family === 'SmolLM2' ? 1.5 : 0.9)
465
- .attr('stroke-opacity', d => d.family === 'SmolLM2' ? 0.5 : 0.30)
466
- .on('mouseenter', handleEnter)
467
- .on('mousemove', handleMove)
468
- .on('mouseleave', handleLeave),
469
- update => update
470
- .attr('cx', d => d.x).attr('cy', d => d.y)
471
- .attr('r', rBase)
472
- .attr('fill', d => familyColors[d.family] || '#aaa')
 
 
 
 
 
 
 
 
 
 
 
473
  .attr('fill-opacity', 0.82)
474
- .attr('stroke', d => d.family === 'SmolLM2' ? '#fff' : (familyColors[d.family] || '#aaa'))
475
- .attr('stroke-width', d => d.family === 'SmolLM2' ? 1.5 : 0.9)
476
- .attr('stroke-opacity', d => d.family === 'SmolLM2' ? 0.5 : 0.30)
477
- .on('mouseenter', handleEnter)
478
- .on('mousemove', handleMove)
479
- .on('mouseleave', handleLeave)
480
- );
481
-
482
- // ─── BREATHING ───
483
- if (animFrame) cancelAnimationFrame(animFrame);
484
- const breathe = () => {
485
- const now = Date.now() * 0.001;
486
- gDots.selectAll('circle').each(function (d) {
487
- d3.select(this).attr('r', rBase + Math.sin(now * 0.5 + d.phase) * rBase * 0.03);
488
- });
489
- animFrame = requestAnimationFrame(breathe);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
490
  };
491
- breathe();
492
- };
493
 
494
- if (window.ResizeObserver) {
495
- new ResizeObserver(() => render()).observe(container);
496
- } else {
497
- window.addEventListener('resize', render);
 
 
498
  }
499
- render();
500
  };
501
 
502
  if (document.readyState === 'loading') {
 
1
+ <div class="d3-verbosity" style="width:100%;margin:10px 0;aspect-ratio:3/1;min-height:260px;"></div>
2
  <script>
3
  (() => {
4
  const ensureD3 = (cb) => {
 
16
  };
17
 
18
  const bootstrap = () => {
19
+ const scriptEl = document.currentScript;
20
+ let container = scriptEl ? scriptEl.previousElementSibling : null;
21
+ if (!(container && container.classList && container.classList.contains('d3-verbosity'))) {
22
+ const cs = Array.from(document.querySelectorAll('.d3-verbosity'))
23
+ .filter(el => !(el.dataset && el.dataset.mounted === 'true'));
24
+ container = cs[cs.length - 1] || null;
 
25
  }
26
+ if (!container) return;
27
+ if (container.dataset.mounted === 'true') return;
28
+ container.dataset.mounted = 'true';
29
+
30
+ // Read data path from HtmlEmbed attribute
31
+ let mountEl = container;
32
+ while (mountEl && !mountEl.getAttribute?.('data-datafiles')) mountEl = mountEl.parentElement;
33
+ const dataAttr = mountEl?.getAttribute?.('data-datafiles');
34
+ const dataPaths = dataAttr
35
+ ? [dataAttr.includes('/') ? dataAttr : `/data/${dataAttr}`]
36
+ : ['/data/rephrasing_metadata.json', './assets/data/rephrasing_metadata.json'];
37
+
38
+ const fetchFirst = async (paths) => {
39
+ for (const p of paths) {
40
+ try { const r = await fetch(p, { cache: 'no-cache' }); if (r.ok) return r.json(); } catch(_) {}
41
+ }
42
+ throw new Error('Data not found');
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
43
  };
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
44
 
45
+ fetchFirst(dataPaths).then(data => buildChart(data)).catch(err => {
46
+ container.innerHTML = `<pre style="color:red;padding:12px;">Error loading data: ${err.message}</pre>`;
47
+ });
 
 
 
 
 
 
 
 
 
 
48
 
49
+ function buildChart(rawData) {
50
+ // ═══════════════════════════════════════════════════════════
51
+ // PARSE JSON into chart-ready objects
52
+ // ═══════════════════════════════════════════════════════════
53
+ const SOURCE_MAP = {
54
+ 'fineweb-edu-hq-20BT': 'FW-Edu HQ',
55
+ 'fineweb-edu-lq-20BT': 'FW-Edu LQ',
56
+ 'dclm-37BT': 'DCLM',
57
+ 'cosmopedia-25BT': 'Cosmopedia'
58
+ };
59
+ const PROMPT_LABELS = {
60
+ 'article': 'Article', 'commentary': 'Commentary', 'discussion': 'Discussion',
61
+ 'faq': 'FAQ', 'math': 'Math', 'table': 'Table', 'tutorial': 'Tutorial',
62
+ 'distill': 'Distill', 'diverse_qa_pairs': 'Diverse QA',
63
+ 'extract_knowledge': 'Extract Knowledge', 'knowledge_list': 'Knowledge List',
64
+ 'wikipedia_style_rephrasing': 'Wikipedia Style',
65
+ 'guided_rewrite_improved': 'Guided Rewrite+',
66
+ 'guided_rewrite_original': 'Guided Rewrite'
67
+ };
68
+ const CAT_MAP = { 'format': 'Format', 'nemotron': 'Nemotron', 'rewire': 'REWIRE' };
69
+
70
+ const getFamily = (m) => {
71
+ const ml = m.toLowerCase();
72
+ if (ml.includes('smollm')) return 'SmolLM2';
73
+ if (ml.includes('gemma')) return 'Gemma';
74
+ if (ml.includes('qwen')) return 'Qwen';
75
+ if (ml.includes('falcon')) return 'Falcon';
76
+ if (ml.includes('granite')) return 'Granite';
77
+ if (ml.includes('llama')) return 'Llama';
78
+ return 'Other';
79
+ };
80
 
81
+ const experiments = rawData.map((d, i) => {
82
+ const [cat, promptFile] = d.prompt.split('/');
83
+ const promptKey = promptFile.replace('.md', '');
84
+ return {
85
+ idx: i,
86
+ cat: CAT_MAP[cat] || cat,
87
+ prompt: PROMPT_LABELS[promptKey] || promptKey,
88
+ model: d.model.split('/').pop(),
89
+ source: SOURCE_MAP[d.source_dataset] || d.source_dataset,
90
+ family: getFamily(d.model),
91
+ compTokens: d.output_tokens / 1e9,
92
+ promptTokens: d.input_tokens / 1e9,
93
+ numDocs: d.num_documents / 1e6,
94
+ dclmDiff: d.dclm_score_difference,
95
+ outDclm: d.output_dclm_score,
96
+ inDclm: d.input_dclm_score,
97
+ eduDiff: d.edu_score_difference,
98
+ outEdu: d.output_edu_score,
99
+ inEdu: d.input_edu_score,
100
+ compPerDoc: d.output_token_count_mean,
101
+ phase: Math.random() * Math.PI * 2
102
+ };
103
+ });
104
 
105
+ // ═══════════════════════════════════════════════════════════
106
+ // MODEL FAMILIES
107
+ // ═══════════════════════════════════════════════════════════
108
+ const familyColors = {
109
+ 'Gemma': '#4EA5B7',
110
+ 'Qwen': '#8B7BE8',
111
+ 'SmolLM2': '#E8C44A',
112
+ 'Falcon': '#E889AB',
113
+ 'Granite': '#5BC0A4',
114
+ 'Llama': '#D09090',
115
+ };
116
+ const familyOrder = ['Gemma','Qwen','SmolLM2','Falcon','Granite','Llama'];
117
+
118
+ // ═══════════════════════════════════════════════════════════
119
+ // FILTER: Only prompts tested across multiple model families
120
+ // ═════��═════════════════════════════════════════════════════
121
+ const focusPrompts = ['Math', 'Table', 'FAQ', 'Tutorial'];
122
+ const filtered = experiments.filter(d => focusPrompts.includes(d.prompt));
123
+
124
+ // Sort rows by median compPerDoc (most verbose on top)
125
+ const promptStats = {};
126
+ focusPrompts.forEach(p => {
127
+ const vals = filtered.filter(d => d.prompt === p).map(d => d.compPerDoc).sort((a,b) => a - b);
128
+ const mid = Math.floor(vals.length / 2);
129
+ promptStats[p] = {
130
+ median: vals.length % 2 ? vals[mid] : (vals[mid-1] + vals[mid]) / 2,
131
+ count: vals.length
132
+ };
133
+ });
134
+ const sortedPrompts = [...focusPrompts].sort((a, b) => promptStats[b].median - promptStats[a].median);
135
+
136
+ // ═══════════════════════════════════════════════════════════
137
+ // TOOLTIP
138
+ // ═══════════════════════════════════════════════════════════
139
+ const fmtB = (v) => v >= 10 ? v.toFixed(0) + 'B' : v.toFixed(1) + 'B';
140
+ const fmtSign = (v, p) => (v >= 0 ? '+' : '') + v.toFixed(p || 3);
141
+ const dCol = (v) => v >= 0 ? '#5BC0A4' : '#E889AB';
142
+ const buildTip = (d) =>
143
+ `<div style="font-weight:800;font-size:13px;">${d.prompt}</div>` +
144
+ `<div style="font-size:11px;color:var(--muted-color);margin-top:-2px;margin-bottom:4px;">` +
145
+ `<span style="display:inline-block;width:8px;height:8px;border-radius:50%;background:${familyColors[d.family]};margin-right:4px;vertical-align:middle;"></span>` +
146
+ `${d.model} \u00b7 ${d.source}</div>` +
147
+ `<div style="padding-top:5px;border-top:1px solid var(--border-color);display:grid;grid-template-columns:auto 1fr;gap:2px 8px;font-size:11.5px;">` +
148
+ `<span style="color:var(--muted-color);">Total output</span><span>${fmtB(d.compTokens)}</span>` +
149
+ `<span style="color:var(--muted-color);">Output/doc</span><span>${Math.round(d.compPerDoc)} tokens</span>` +
150
+ `<span style="color:var(--muted-color);">Docs</span><span>${d.numDocs.toFixed(1)}M</span>` +
151
+ `</div>` +
152
+ `<div style="margin-top:5px;padding-top:5px;border-top:1px solid var(--border-color);display:grid;grid-template-columns:auto 1fr;gap:2px 8px;font-size:11.5px;">` +
153
+ `<span style="color:var(--muted-color);">DCLM</span>` +
154
+ `<span>${d.inDclm.toFixed(3)} \u2192 ${d.outDclm.toFixed(3)} <b style="color:${dCol(d.dclmDiff)};">${fmtSign(d.dclmDiff)}</b></span>` +
155
+ `<span style="color:var(--muted-color);">Edu</span>` +
156
+ `<span>${d.inEdu.toFixed(2)} \u2192 ${d.outEdu.toFixed(2)} <b style="color:${dCol(d.eduDiff)};">${fmtSign(d.eduDiff, 2)}</b></span>` +
157
+ `</div>`;
158
+
159
+ // ═══════════════════════════════════════════════════════════
160
+ // SVG
161
+ // ═══════════════════════════════════════════════════════════
162
+ const svg = d3.select(container).append('svg')
163
+ .attr('width', '100%')
164
+ .style('display', 'block')
165
+ .style('cursor', 'crosshair');
166
+
167
+ let animFrame = null;
168
+
169
+ const render = () => {
170
+ const width = container.clientWidth || 800;
171
+ const height = Math.max(260, Math.round(width / 3));
172
+ svg.attr('width', width).attr('height', height);
173
+
174
+ const isDark = document.documentElement.getAttribute('data-theme') === 'dark';
175
+ const textColor = isDark ? 'rgba(255,255,255,0.72)' : 'rgba(0,0,0,0.62)';
176
+ const mutedText = isDark ? 'rgba(255,255,255,0.22)' : 'rgba(0,0,0,0.18)';
177
+ const subtleText = isDark ? 'rgba(255,255,255,0.12)' : 'rgba(0,0,0,0.08)';
178
+ const refLine = isDark ? 'rgba(255,255,255,0.10)' : 'rgba(0,0,0,0.07)';
179
+ const bandEven = isDark ? 'rgba(255,255,255,0.022)' : 'rgba(0,0,0,0.018)';
180
+ const glowColor = isDark ? 'rgba(255,255,255,0.35)' : 'rgba(0,0,0,0.25)';
181
+ const smolHighlight = isDark ? 'rgba(232,196,74,0.18)' : 'rgba(232,196,74,0.12)';
182
+
183
+ const fontSize = Math.max(8, Math.min(12, width / 80));
184
+
185
+ // ─── LAYOUT ───
186
+ const labelW = Math.max(68, width * 0.085);
187
+ const pl = labelW + 12;
188
+ const pr = width * 0.965;
189
+ const pt = height * 0.20;
190
+ const pb = height * 0.86;
191
+ const plotH = pb - pt;
192
+ const rowH = plotH / sortedPrompts.length;
193
+
194
+ const rowCenter = {};
195
+ sortedPrompts.forEach((p, i) => { rowCenter[p] = pt + (i + 0.5) * rowH; });
196
+
197
+ // ─── X SCALE: completion tokens per document (log) ───
198
+ const xScale = d3.scaleLog().base(10)
199
+ .domain([15, 1000])
200
+ .range([pl, pr]);
201
+
202
+ const rBase = Math.max(3.5, Math.min(8, width * 0.007));
203
+
204
+ // ─── FORCE SIMULATION ───
205
+ filtered.forEach(d => {
206
+ d.tx = xScale(Math.max(15, d.compPerDoc));
207
+ d.ty = rowCenter[d.prompt];
208
+ d.x = d.tx + (Math.random() - 0.5) * 6;
209
+ d.y = d.ty + (Math.random() - 0.5) * rowH * 0.3;
210
+ });
211
 
212
+ const sim = d3.forceSimulation(filtered)
213
+ .force('x', d3.forceX(d => d.tx).strength(0.72))
214
+ .force('y', d3.forceY(d => d.ty).strength(0.55))
215
+ .force('collide', d3.forceCollide(rBase + 1.5).iterations(5))
216
+ .stop();
217
+ for (let i = 0; i < 250; i++) sim.tick();
218
+
219
+ filtered.forEach(d => {
220
+ d.x = Math.max(pl + rBase, Math.min(pr - rBase, d.x));
221
+ const yMin = rowCenter[d.prompt] - rowH * 0.46;
222
+ const yMax = rowCenter[d.prompt] + rowH * 0.46;
223
+ d.y = Math.max(yMin, Math.min(yMax, d.y));
224
+ });
225
 
226
+ // ─── BG STARS ───
227
+ const bgCol = (() => {
228
+ const i01 = d3.interpolateRgb(d3.rgb(78,165,183), d3.rgb(206,192,250));
229
+ const i12 = d3.interpolateRgb(d3.rgb(206,192,250), d3.rgb(232,137,171));
230
+ return (v) => { const t = Math.max(0, Math.min(1, v)); return t <= 0.5 ? i01(t / 0.5) : i12((t - 0.5) / 0.5); };
231
+ })();
232
+ const gBg = svg.selectAll('g.bg').data([0]).join('g').attr('class', 'bg');
233
+ if (!gBg.selectAll('circle').size()) {
234
+ const stars = d3.range(50).map(() => ({
235
+ x: Math.random() * width, y: Math.random() * height,
236
+ z: Math.random(), r: 0.3 + Math.random() * 0.6
237
+ }));
238
+ gBg.selectAll('circle').data(stars).join('circle')
239
+ .attr('cx', d => d.x).attr('cy', d => d.y).attr('r', d => d.r)
240
+ .attr('fill', d => bgCol(d.z)).attr('fill-opacity', d => 0.03 + d.z * 0.04);
241
  }
242
 
243
+ // ─── ROW BANDS & LABELS ───
244
+ const gRows = svg.selectAll('g.rows').data([0]).join('g').attr('class', 'rows');
245
+ gRows.selectAll('*').remove();
246
+
247
+ sortedPrompts.forEach((p, i) => {
248
+ const y = rowCenter[p];
249
+
250
+ if (i % 2 === 0) {
251
+ gRows.append('rect')
252
+ .attr('x', pl - 6).attr('y', y - rowH / 2)
253
+ .attr('width', pr - pl + 12).attr('height', rowH)
254
+ .attr('fill', bandEven).attr('rx', 4);
255
+ }
256
+ if (i > 0) {
257
+ gRows.append('line')
258
+ .attr('x1', pl - 2).attr('x2', pr + 2)
259
+ .attr('y1', y - rowH / 2).attr('y2', y - rowH / 2)
260
+ .attr('stroke', refLine).attr('stroke-width', 0.5);
261
+ }
262
+
263
+ // SmolLM2 highlight zone
264
+ const smolExp = filtered.filter(d => d.prompt === p && d.family === 'SmolLM2');
265
+ if (smolExp.length) {
266
+ const smolX = smolExp[0].x;
267
+ gRows.append('rect')
268
+ .attr('x', smolX - rBase * 2.5).attr('y', y - rowH * 0.42)
269
+ .attr('width', rBase * 5).attr('height', rowH * 0.84)
270
+ .attr('fill', smolHighlight).attr('rx', rBase * 2);
271
+ }
272
+
273
+ // Label
274
+ gRows.append('text')
275
+ .attr('x', pl - 10).attr('y', y)
276
+ .attr('text-anchor', 'end')
277
+ .attr('dominant-baseline', 'central')
278
+ .attr('fill', textColor)
279
+ .attr('font-size', fontSize + 'px')
280
+ .attr('font-weight', '700')
281
  .attr('font-family', 'system-ui, -apple-system, sans-serif')
282
+ .attr('letter-spacing', '-0.2px')
283
+ .text(p);
284
+ });
285
+
286
+ // ─── X AXIS TICKS ───
287
+ const gRef = svg.selectAll('g.ref').data([0]).join('g').attr('class', 'ref');
288
+ gRef.selectAll('*').remove();
289
+
290
+ [20, 50, 100, 200, 500].forEach(v => {
291
+ const x = xScale(v);
292
+ if (x > pl && x < pr) {
293
+ gRef.append('line')
294
+ .attr('x1', x).attr('x2', x)
295
+ .attr('y1', pb).attr('y2', pb + 3)
296
+ .attr('stroke', mutedText).attr('stroke-width', 0.7);
297
+ gRef.append('text')
298
+ .attr('x', x).attr('y', pb + 5)
299
+ .attr('text-anchor', 'middle')
300
+ .attr('dominant-baseline', 'hanging')
301
+ .attr('fill', mutedText)
302
+ .attr('font-size', (fontSize * 0.65) + 'px')
303
+ .attr('font-family', 'system-ui, -apple-system, sans-serif')
304
+ .text(v);
305
+ gRef.append('line')
306
+ .attr('x1', x).attr('x2', x)
307
+ .attr('y1', pt).attr('y2', pb)
308
+ .attr('stroke', subtleText).attr('stroke-width', 0.4)
309
+ .attr('stroke-dasharray', '2,6');
310
+ }
311
+ });
312
 
313
+ // Axis label
314
+ gRef.append('text')
315
+ .attr('x', pr).attr('y', pb + 16)
316
+ .attr('text-anchor', 'end')
317
+ .attr('fill', mutedText)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
318
  .attr('font-size', (fontSize * 0.72) + 'px')
319
+ .attr('font-weight', '600')
320
  .attr('font-family', 'system-ui, -apple-system, sans-serif')
321
+ .text('Output tokens / document \u2192');
322
+
323
+ // "concise" / "verbose" labels
324
+ gRef.append('text')
325
+ .attr('x', pl + 6).attr('y', pt - 5)
326
+ .attr('text-anchor', 'start')
327
+ .attr('fill', subtleText)
328
+ .attr('font-size', (fontSize * 0.62) + 'px')
329
+ .attr('font-style', 'italic')
330
+ .attr('font-family', 'system-ui, -apple-system, sans-serif')
331
+ .text('\u2190 concise');
332
 
333
+ gRef.append('text')
334
+ .attr('x', pr - 6).attr('y', pt - 5)
335
+ .attr('text-anchor', 'end')
336
+ .attr('fill', subtleText)
337
+ .attr('font-size', (fontSize * 0.62) + 'px')
338
+ .attr('font-style', 'italic')
339
+ .attr('font-family', 'system-ui, -apple-system, sans-serif')
340
+ .text('verbose \u2192');
341
+
342
+ // ─── MODEL FAMILY LEGEND ───
343
+ const gLeg = svg.selectAll('g.legend').data([0]).join('g').attr('class', 'legend');
344
+ gLeg.selectAll('*').remove();
345
+ const legDotR = Math.max(3, fontSize * 0.35);
346
+ const legGap = Math.max(68, width * 0.082);
347
+ const totalLegW = familyOrder.length * legGap;
348
+ const legStartX = Math.max(pl, (width - totalLegW) / 2);
349
+ const legY = height * 0.055;
350
+
351
+ familyOrder.forEach((fam, i) => {
352
+ const lx = legStartX + i * legGap;
353
+ gLeg.append('circle')
354
+ .attr('cx', lx).attr('cy', legY)
355
+ .attr('r', legDotR)
356
+ .attr('fill', familyColors[fam]).attr('fill-opacity', 0.85);
357
+ gLeg.append('text')
358
+ .attr('x', lx + legDotR + 4).attr('y', legY)
359
+ .attr('dominant-baseline', 'central')
360
+ .attr('fill', isDark ? 'rgba(255,255,255,0.48)' : 'rgba(0,0,0,0.42)')
361
+ .attr('font-size', (fontSize * 0.72) + 'px')
362
+ .attr('font-weight', fam === 'SmolLM2' ? '700' : '500')
363
+ .attr('font-family', 'system-ui, -apple-system, sans-serif')
364
+ .text(fam);
365
  });
 
 
 
 
 
366
 
367
+ // ─── TOOLTIP ───
368
+ container.style.position = container.style.position || 'relative';
369
+ let tip = container.querySelector('.d3-tooltip');
370
+ let tipInner;
371
+ if (!tip) {
372
+ tip = document.createElement('div');
373
+ tip.className = 'd3-tooltip';
374
+ Object.assign(tip.style, {
375
+ position: 'absolute', top: '0px', left: '0px',
376
+ transform: 'translate(-9999px, -9999px)',
377
+ pointerEvents: 'none',
378
+ padding: '10px 14px', borderRadius: '12px',
379
+ fontSize: '12px', lineHeight: '1.4',
380
+ border: '1px solid var(--border-color)',
381
+ background: 'var(--surface-bg)',
382
+ color: 'var(--text-color)',
383
+ boxShadow: '0 8px 32px rgba(0,0,0,.28), 0 2px 8px rgba(0,0,0,.12)',
384
+ opacity: '0', transition: 'opacity .12s ease',
385
+ backdropFilter: 'saturate(1.12) blur(8px)',
386
+ zIndex: '20', maxWidth: '320px'
387
+ });
388
+ tipInner = document.createElement('div');
389
+ tipInner.className = 'd3-tooltip__inner';
390
+ Object.assign(tipInner.style, {
391
+ textAlign: 'left', display: 'flex', flexDirection: 'column',
392
+ gap: '4px', minWidth: '220px'
393
+ });
394
+ tip.appendChild(tipInner);
395
+ container.appendChild(tip);
396
+ } else {
397
+ tipInner = tip.querySelector('.d3-tooltip__inner') || tip;
398
+ }
 
 
 
 
 
 
399
 
400
+ // ─── DOTS ───
401
+ const gDots = svg.selectAll('g.dots').data([0]).join('g').attr('class', 'dots');
402
+
403
+ const handleEnter = function (ev, d) {
404
+ gDots.selectAll('circle').attr('fill-opacity', 0.12).attr('stroke-opacity', 0.05);
405
+ gDots.selectAll('circle')
406
+ .filter(c => c.family === d.family)
407
+ .attr('fill-opacity', 0.60).attr('stroke-opacity', 0.4);
408
+ d3.select(this)
409
+ .raise()
410
+ .attr('fill-opacity', 1)
411
+ .attr('stroke-opacity', 1)
412
+ .style('filter', `drop-shadow(0 0 8px ${glowColor})`)
413
+ .transition().duration(90).ease(d3.easeCubicOut)
414
+ .attr('r', rBase * 1.5);
415
+ tipInner.innerHTML = buildTip(d);
416
+ tip.style.opacity = '1';
417
+ };
418
+ const handleMove = (ev) => {
419
+ const [mx, my] = d3.pointer(ev, container);
420
+ const bw = tip.offsetWidth || 260;
421
+ const bh = tip.offsetHeight || 180;
422
+ const ox = (mx + bw + 20 > width) ? -(bw + 12) : 12;
423
+ const oy = (my + bh + 20 > height) ? -(bh + 12) : 14;
424
+ tip.style.transform = `translate(${Math.round(mx + ox)}px, ${Math.round(my + oy)}px)`;
425
+ };
426
+ const handleLeave = function () {
427
+ gDots.selectAll('circle')
428
  .attr('fill-opacity', 0.82)
429
+ .attr('stroke-opacity', 0.30);
430
+ d3.select(this)
431
+ .style('filter', null)
432
+ .transition().duration(90).ease(d3.easeCubicOut)
433
+ .attr('r', rBase);
434
+ tip.style.opacity = '0';
435
+ tip.style.transform = 'translate(-9999px, -9999px)';
436
+ };
437
+
438
+ gDots.selectAll('circle').data(filtered, d => d.idx)
439
+ .join(
440
+ enter => enter.append('circle')
441
+ .attr('cx', d => d.x).attr('cy', d => d.y)
442
+ .attr('r', rBase)
443
+ .attr('fill', d => familyColors[d.family] || '#aaa')
444
+ .attr('fill-opacity', 0.82)
445
+ .attr('stroke', d => d.family === 'SmolLM2' ? '#fff' : (familyColors[d.family] || '#aaa'))
446
+ .attr('stroke-width', d => d.family === 'SmolLM2' ? 1.5 : 0.9)
447
+ .attr('stroke-opacity', d => d.family === 'SmolLM2' ? 0.5 : 0.30)
448
+ .on('mouseenter', handleEnter)
449
+ .on('mousemove', handleMove)
450
+ .on('mouseleave', handleLeave),
451
+ update => update
452
+ .attr('cx', d => d.x).attr('cy', d => d.y)
453
+ .attr('r', rBase)
454
+ .attr('fill', d => familyColors[d.family] || '#aaa')
455
+ .attr('fill-opacity', 0.82)
456
+ .attr('stroke', d => d.family === 'SmolLM2' ? '#fff' : (familyColors[d.family] || '#aaa'))
457
+ .attr('stroke-width', d => d.family === 'SmolLM2' ? 1.5 : 0.9)
458
+ .attr('stroke-opacity', d => d.family === 'SmolLM2' ? 0.5 : 0.30)
459
+ .on('mouseenter', handleEnter)
460
+ .on('mousemove', handleMove)
461
+ .on('mouseleave', handleLeave)
462
+ );
463
+
464
+ // ─── BREATHING ───
465
+ if (animFrame) cancelAnimationFrame(animFrame);
466
+ const breathe = () => {
467
+ const now = Date.now() * 0.001;
468
+ gDots.selectAll('circle').each(function (d) {
469
+ d3.select(this).attr('r', rBase + Math.sin(now * 0.5 + d.phase) * rBase * 0.03);
470
+ });
471
+ animFrame = requestAnimationFrame(breathe);
472
+ };
473
+ breathe();
474
  };
 
 
475
 
476
+ if (window.ResizeObserver) {
477
+ new ResizeObserver(() => render()).observe(container);
478
+ } else {
479
+ window.addEventListener('resize', render);
480
+ }
481
+ render();
482
  }
 
483
  };
484
 
485
  if (document.readyState === 'loading') {